函数是一等公民

Dart 是一门完备的面向对象的编程语言,所以函数也是对象并且其类型是 Function。所以这就表示函数也可以赋值给变量,也就是说可以将函数作为参数进行传递。

一个函数的示例

    bool isBiggerThan10(int number) {
        return number > 10;
     }

虽然加上函数的返回值是个好习惯,但是 Dart 并不一定非要返回值,因为 Dart 会尝试推断函数的返回值。如果没有明确返回值,则会返回 null(因为 Dart 中所有未定义的变量都是 null)。

例如:

    isBiggerThan10(int number) {
        return number > 10;
     }

可以通过

    print(isBiggerThan10.runtimeType);

来获取此函数的类型。

箭头函数

当然了,为了简化函数的写法,Dart 也支持箭头函数,上面的例子可以写作为:

    bool isBiggerThan10(int number) => number > 10;

但不可写作下面这样子(number > 10 后面没有分号)。

    isBiggerThan10(int number) => {
        number > 10
     };

这段代码相当于:

    isBiggerThan10(int number) {
        return {number > 10};
     };

即:函数只包含一个表达式(重点),可以简写成胖箭头形式,=> 后面的表达式将作为函数的返回结果(或者返回 null)。

参数

和其他语言一样,函数定义的时候都可以指定参数列表

    bool isBiggerThanX(int number, int x) {
        return number > x;
     }

但是如果参数很多的话,就很有可能分不清楚要传入的参数对应的是那个了。Dart 有了具名参数。

具名参数
    bool isBiggerThanX({int number, int x}) {
        return number > x;
     }

调用

    isBiggerThanX(number:5, x:9);

还可以有默认(可选)参数

    bool isBiggerThanX({int number, int x = 10}) {
        if(number == null ){
            return false;
        }
        return number > x;
     }

调用

    isBiggerThanX(number:5)

此时 x 的值默认就是 10.

再调用

    isBiggerThanX(number:5, x:9)

此时 x 的值就是 9.

再调用

isBiggerThanX()

此时 x 的值就是 10, 所以说具名参数都是可选的。但是可以通过增加 @required 来表明是必填项, 但是好像编译时并没有出错(?)。

    bool isBiggerThanX({@required int number, int x = 10}) {
        return number > x;
     }
可选参数

那有时候我想要的可选参数是可传可不传的,而参数比较少,不想使用具名的默认参数,怎么办呢?那就是用可选参数。

    bool isBiggerThanX(int number, [int x]) {
        if(x != null) {
            return number > x;
        }
        return true;
     }

当然也可以这样

    bool isBiggerThanX(int number, [int x = 9]) {
        return true;
     }

这个时候,x 不传的话就是 9 了。

但是可选参数必须放在最后一个。对于具名的默认参数,可以不放到最后一个,任意都可以设置默认参数。

匿名函数

上面的函数都起了名字,Dart 当然支持不起名字的函数

    List list = [1, 2, 3];
    list.forEach((item) {
        print('item is : $item');
     }

垃圾分类规则

根据规定,单位或个人违反规定随意投放生活垃圾的,个人罚款¥100以上¥200以下,单位罚款¥5000以上¥20000以下,并将不良信息纳入个人征信系统

垃圾分类的时代已经到来,不知道大家的理论知识有没有储备好呢?

首先,西安的四色垃圾桶了解一下~

红、绿、蓝、灰,分别对应可回收物、餐厨垃圾、有害垃圾、其他垃圾

为了帮大家轻松实现“分类自由”,我们为大家找到了一份生活常见垃圾分类表供大家参考学习~

有害垃圾:

  • 废旧灯管、笔芯、硒鼓、墨盒
  • 废旧电池(充电电池、纽扣电池、蓄电池)
  • 油漆桶、杀虫剂类物品(有残留需提前密封再投放)
  • 过期或废弃的药物

投放要求:

  • 投放时请注意轻放;
  • 易破损的请连带包装或包裹后轻投放
  • 如易挥发,请密封后投放

可回收垃圾:

  • 各种瓶子(包括易拉罐),牛奶盒,但请记得不要有剩余液体,要干干净净
  • 书籍、报纸、宣传册、打印的纸张、纸箱纸板等纸质类物品
  • 用完的洗发水、沐浴液、洗衣液等瓶瓶罐罐
  •   旧衣物以及床单被套等

投放要求:

  • 轻投轻放
  • 尽量保持清洁干燥,避免污染
  • 立体包装需清空内容物,清洁压扁后投放
  • 易破损或有尖锐边角的应包裹后投放

厨余垃圾

  • 所有剩在碗里不吃的食物
  • 啃过的鸭脖鸡爪等系列小型骨头
  • 吃完的葡萄皮等任何瓜皮果核
  • 花卉绿植

投放要求:

  • 厨余垃圾应当沥干后投放
  • 有包装的垃圾应将包装物去除后分类投放,包装物请投放到对应的垃圾容器

其他垃圾 :

  • 所有你吃剩下的油滋滋的外卖盒、食物袋等(里面不能有食物残留)
  • 不易腐烂的大骨头、海鲜壳之类的
  • 遭到污染的纸巾湿巾
  • 吸完烟,扔到这里
  • 陶瓷及花盆类物品
  • 一次性餐具、纸杯(包括奶茶杯等饮料杯)、口香糖
  • 一次性干电池

投放要求:

  • 尽量沥干水分
  • 难以辨识类别的生活垃圾投入其他垃圾容器内

===============================================================

最后做一道测试题考考大家:

请问曾经一度登上头条的未喝完的珍珠奶茶属于什么垃圾?(多选题)

A:厨余垃圾        B:可回收垃圾
C:有害垃圾        D:其他垃圾

答案:ABD~

第1步:首先把剩余奶茶液体倒进下水口;
第2步:将奶茶里的珍珠、椰果、布丁等加料丢进绿色的“厨余垃圾”桶里;
第3步:把杯子和吸管等塑料制品丢进灰色的“其他垃圾”桶里;

附加条:如果你的饮料上面是有盖子,那么盖子需要扔进蓝色的“可回收物”垃圾桶里,因为奶茶的盖子是可回收的!

从垃圾分类开始,让低碳生活真正地融入到我们的生活中,
大家的每一次正确投放,都在为美丽的西安、美丽的葡萄城贡献自己的一份力量~

以 Dart 为例:
Dart 中的 DateTime 周一为一周起始日,索引是 1,周日为结束日索引是 7.月索引从 1 到 12.
当前:2019-08-06
当月:2019-08-01
下月:2019-09-01

startWeekday : 当月第一天的周几 – 月开始日期索引(开始日期的周几)

endWeekday : 下月第一天的周几 – 月开始日期索引(开始日期的周几)

当月最后一天 + startWeekday + (7 – endWeekday) = 当月日历显示日期数量

以下图为例:
当月:2019-10-01 Dart 的周所以开始是 7,结束是 6.
startWeekday : 则周几为 2,(29, 30, 1)则索引为([7, 1, 2]),
endWeekday : 则周几为 5,(27, 28, 29, 30, 31, 1)则索引为([7, 1, 2, 3, 4, 5]),
最后一天:31,
则:31 + 2 + (7 – 5) = 35天。
所以日历最终显示为:上月的 29 30 两天+当月的 31 天+下月的1 2两天,共 35 天

升级到 xcode 10 以后,xcode 已经不包含这个 libstdc++.6.0.9 文件了 但是有些项目没有升级,还需要依赖此文件,所以会导致编译不过。
下载地址:
http://img.yimity.com/libstdc++.6.0.9.tbd.zip
使用方法:解压后放到
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib
或者报错的信息显示的目录中。即可。

segmentfault 看到一篇文章,讲 API 设计美学的文章,看到一点觉得很不错,虽然也知道应该这么做,但是一个例子胜千言啊,所以将这个内容搬过来,也加深自己的理解。感谢。

这一点就是函数的可扩展性

可扩展性,就是建议遵守开放-封闭原则。对扩展开放,对修改关闭。比如jQuery的$.fn和$.fn.extend()。

说一个简单的例子-计算加薪额度

var addMoney = (function () {
    //定义策略类
    var strategies = {
        A:function(money){
            return money + 2000;
        },
        B:function(money){
            return money + 1000;
        }
    };
    //暴露接口
    return {
        //根据等级和现工资,输入加薪后的工资
        compute:function(lv,money){
            return strategies[lv](money)
        }
    };
})();

//比如:等级为A,5000+2000
console.log(addMoney.compute('A',5000))//7000
//比如:等级为B,20000+1000
console.log(addMoney.compute('B',20000))//21000
代码看着没有问题,但是如果以后需求要增加C等级呢?这就不得不修改strategies。在里面增加方法。
如下

var strategies = {
    A:function(money){
        return money + 2000;
    },
    B:function(money){
        return money + 1000;
    },
    C:function(money){
        return money + 500;
    }
};

这样实现也简单,如果以后要增加S等级呢?又得改strategies。这里还有一个问题就是,如果增加的C等级只有在A模块需要用到,在B模块不会出现,那么在B模块引用addMoney的时候,又会把C等级的计算方式也引入进去,造成不必要的资源浪费。
建议的方式是,设置一个接口,扩展strategies。

var addMoney = (function () {
    //定义策略类
    let strategies = {
        A:function(money){
            return money + 2000;
        },
        B:function(money){
            return money + 1000;
        }
    };
    //暴露接口
    return {
        //根据等级和现工资,输入加薪后的工资
        compute:function(lv,money){
            return strategies[lv](money)
        },
        //扩展等级
        addRule:function(lv,fn){
            strategies[lv]=fn;
        }
    };
})();

//增加C等级的调用

addMoney.addRule('C',function(money){
    return money + 500;
});
console.log(addMoney.compute('C',20000))//20500

今天看到一个问题,问一个怎么实现

a(3)(4)(4)
// 48

这样子的问题。
那看到这个问题的第一反应应该就是函数的柯里化。
维基百科中对函数柯里化的解释是这样的:

柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

函数柯里化有什么作用呢,为什么要柯里化呢。其实也没有什么作用,一般编程中,基本不会用到柯里化,但是假如在函数式编程中,还是很有用的。另一方面,就是面试的时候很多面试官会问。
那么我们看看下面实现的几种例子:

1. 如果固定函数的调用次数

const a = function(x) {
    return function(y) {
      return function(z) {
          return x * y * z;
      };
    };
};

改成 ES6 的写法会更简单

const result = x => y => z => x * y * z;

那这种方式只能实现固定三次调用,如果想有更通用的方法呢?那就看方法二:

2. 通用方法

function curry(fn) {
    const len = fn.length;
    return function curried() {
        const args = Array.prototype.slice.call(arguments);
        if (args.length >= len) {
            return fn.apply(this, args);
        }            
        return function () {
            return curried.apply(this, args.concat(Array.prototype.slice.call(arguments)));
        };
    };
}

如果使用 ES6 的写法,可以简化为(使用了 ES6 的剩余参数(Rest Parameters)):

function curry(fn, ...args){
    return args.length === fn.length ? fn(...args) : (...next_args) => curry(fn, ...args, ...next_args); 
}

然后将需要柯里化的函数传入,即可得到一个柯里化的函数。

const result = curry(function (a, b, c) {
    return a * b * c;
});

那同样的,可以得到

const result2 = curry(function (a, b, c) {
    return a + b + c;
});

可是,如果我想得到

let x = a(3)(4)
let y = x(4);
console.log(x); // 12
console.log(y); // 48

又该怎么办呢?有些人想既然 x 输出为 12 了,那就肯定不能再被调用了,所以 x(4) 肯定会报错。
但是假如 数字12, 并不是一个真正的数字呢?来看例子:

3. 特殊的柯里化

 function currying(x){
  var sum = x
  var curried = function(y){
    sum = sum * y
    return curried
  }
  curried.toString=function(){
    return sum 
  }
  return curried
}

调用

console.log(currying(1))   //1
console.log(currying(1)(2))  //2
console.log(currying(1)(2)(3))  //6
console.log(currying(1)(2)(3)(4))  //24

就是可以得到的,那是为什么呢?主要是利用了函数的 toString 方法。实现了障眼法,在控制台中调用的时候,因为返回的是函数,所以会调用函数的 toString 方法,故而就会显示出来数字。但是在实际代码调用过程中,其实返回的还是函数。所以,这种不算严格的柯里化。只是形式上类似而已。并且还会存在单例的问题,也即此函数任何时候调用,都是同一个值累积。
对于此函数还是好理解的,但是对于第二种通用柯里化其实比较难以理解。那么就大概解释下吧。

先解释两个概念,可能很多人会弄混乱。
1. 函数 a 执行之后返回一个函数 b,函数 b 中 的 arguments 是 调用函数 b 时候传入的参数。
2. Array.prototype.slice.call 是在类数组上调用数组的 slice 方法,将类数组变为真正的数组,例如调用函数是传入的 arguments 就是一个类数组,他是没有 slice 等数组的方法的,所以使用 Array.prototype.slice.call 来将其变为真正的数组。
给 1 举个例子,2 的例子上面的通用函数里面已经有了。

function a(){
    return function (){
        console.log(arguments);
    }
}
var b = a();

此时,b 应该是一个函数.

function (){
    console.log(arguments);
}

那么这个时候再调用b(1,2,3,4,5);
那 在刚才定义的那个函数中 arguments 应该是 ≈[1,2,3,4,5];这个是很重要的一点,很多人这里会被弄混。
那接下来继续看通用柯里化函数。

当调用

const a = function (a, b, c) {
    return a + b + c;
}
const result2 = curry(a);

时。此时 result2 应该是一个函数。

result2 = function curried() {
        const args = Array.prototype.slice.call(arguments);
        if (args.length >= len) {
            return fn.apply(this, args);
        }            
        return function () {
            return curried.apply(this, args.concat(Array.prototype.slice.call(arguments)));
        };
    };
});

但是在这个函数中,却使用了闭包保留了函数 a 的参数,对于此例子来说,就是 a,b,c, 它的长度是 3.即,我调用 result2(3) 的时候,是能够访问到调用 curry(a) 时,a 传的参数(的个数)的。

那,当调用 result2(3)(4)(4);时,是个什么情况呢?

先看第一步let result3 = result2(3);此时会执行 curried() 这个函数。而执行此函数的时候,

const args = Array.prototype.slice.call(arguments);

中的 arguments 就是 3.所以 [3] 的长度小于刚才 a 的长度 3.那么就会继续返回一个函数。此时

result3 = function () {
    return curried.apply(this, args.concat(Array.prototype.slice.call(arguments)));
}

在调用let result4 = result3(4); 和调用 result2(3) 是一样的结果,但是 由于每次都是通过 curried.apply 来调用 curried,并且每次调用 curried 的时候,都会

args.concat(Array.prototype.slice.call(arguments))

所以每次调用,curried 的参数都会增加。上面的例子,

const args = Array.prototype.slice.call(arguments);

中的 arguments 就应该是[3, 4]了。
所以当最后一次调用的时候 args.length >= len 的条件为真。所以就调用了 fn.apply(this, args) 等同于 fn(3,4,4) 最终就会返回相应的结果了。

此文章主要的灵感来自于:segmentfault,使用了其中的函数代码并做修改,特此感谢。