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,使用了其中的函数代码并做修改,特此感谢。

在 Angular 1 中,自定义 Filter 可以这么做。

angular
  .module('app')
  .filter('leftPad', function(value, anotherValue){
    // do something with `value` and `anotherValue`
    // and return a value
});

然后用的时候可以这么用

<p>{{ num | leftPad:searchValue }}</p>

然后 controller 中可以这么用

controller($filter) {
    var numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
    this.numbers = numbers.map(number => $filter('leftPad')(number, searchValue));
  }

那么在 Angular 2 中怎么做呢?
首先一点,在 Angular 2 中已经没有 filter 的概念了,取而代之的是 Pipe 的概念。但是其本质和 filter 是一样的。我们来看看具体怎么使用。
当然了,首先还是先定义。主要的内容就是要实现一个 PipeTransform 的 transform 方法。

// leftPad.pipe.ts

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({name: 'leftPad'})
export class LeftPad implements PipeTransform {
  transform(value: number, anotherValue: number): string {
        // do something with `value` and `anotherValue`
        // and return a value
    }
}

然后在模板中使用的方法就是:

<p>{{ num | leftPad:searchValue }}</p>

那如果想在 component 中使用,怎么用呢?其实也很简单

export class App {
  constructor(private leftPad: LeftPad) {
    let numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
    this.numbers = numbers.map(number => this.leftPad.transform(number, searchValue));
  }
}

调用通过 ID 注入进来的 LeftPad 的 实例的 transform 方法即可。
那如果只需要一个参数怎么办呢?
一种就是直接删掉后面的一个参数,例如删掉 searchValue 这个参数。另一种就是兼容两种参数调用,定义 pipe 的时候给不是必须的参数增加一个可选的 ? 标志即可。

最后,在使用的时候,不要忘记添加 providers 哦。

NPM 现在变得越来越流行了,但是很多人可能只是用了其中一小部分的功能,但是 NPM 其实有一大票功能可以用,不信你试试 npm -help 看看。

而本文的主要目的就是介绍 npm version 命令,此命令用来更改项目的版本号。这个版本好就是项目中 package.json 文件中的 version 字段。
一般情况下,都类似于这样一段 json。

{
    "name"           : "npm-version",
    "description"    : "npm version command",
    "version"        : "1.1.1611"
}

怎么用呢?
例如,
你修复了一个 bug,要增加一个最小的版本号,那么

npm version patch

如果想要更新次要版本号的话,那么

npm version minor

更或者,你要更新主版本号的话,那么

npm version major

哈哈哈,是不是不用手动更新这个文件了?

还有一点点

如果你正好用 git 来管理你的项目,那么 npm version 也可以顺便创建一个版本 commit 和 tag 。

钩子

最神奇的事情就是给了我们 pre 和 post 这两个钩子。

即:
在 npm version 执行之前,pre 这个钩子,就会被执行,而在 npm version 执行之后,post 这个钩子也会被执行。
所以呢,如果你想在更新版本号之后在执行一些操作,那么就应该这样写。

"scripts" : {
	"postversion" : "git push && git push --tags"
}

这样子,在更改了版本号之后,就会自动执行这些 git 命令了。
那么想在更新版本号之前呢?咋办啊?

"scripts" : {
	"start" : "node app.js",
	"pretest":"rm -Rf build && gulp build && cd build && npm install --production",
	"test":"./test/run.sh",
	"preversion":"npm test",
	"postversion" : "git push && git push --tags"
}

看,完成了,一个还算基本完成了的 package.json 文件,在改变版本号之前,先测试一下嘛。

如果想看更多的 npm version 的信息,请访问:https://docs.npmjs.com/cli/version

来源:http://blog.js-republic.com/npm-version-is-cool-you-should-use-it/

1. 在Windows中添加一个HOME环境变量,值为%USERPROFILE%,如下图:
environment variable
2. 在 "开始>运行" 中打开 %Home%,新建一个名为 _netrc 的文件。

3. 用文本编辑器打开 _netrc 文件,输入 Git 服务器名、用户名、密码,并保存。示例如下:

machine git.xxx.com
login git_user
password git_pwd

OK, 试试吧,还需要不需要用户名密码了。

iscolor 是否是颜色 接受正常的颜色关键字和十六位表示法以及RGB表示法等。
isnumber 是否是数字
isstring 是否是字符串
iskeyword 是否是关键字 这个没搞明白到底用来判断什么东西。
isurl 是否是 url  例如 url(...),貌似必须这样子才行。

ispixel 是否是像素  应该判断给定的值是否是 数字+px 的。
ispercentage 是否是百分比 应该判断给定的值是否是 数字+% 的。
isem 是否是 em
isunit 是否是某个单位的,两个参数 如果待验证的值为指定单位的数字则返回 true

用法:(同时包含了 when 的一种用法)
.mixin (@a) when (isnumber(@a)) {
padding:@a;
}
.x {
.mixin(5);
}
如果 .x 中的 mixin 给的是数字的话,输出:
.x {
padding:5;
}
否则什么都不输出。

面向对象编程

封装

对象生成的原始模式

var Cat = {
    name  : '',
    color : ''
}

然后生成实例对象,

var cat1 = {};

cat1.name = '大毛';
cat1.color = '黄色';

var cat2 = {};

cat2.name = '二毛';
cat2.color = '黑色';

很简单,但问题是,这样生成的“实例对象”其实和原型对象之间基本没有任何联系。而且生成多个实例的时候,没有简洁的方法,会非常麻烦。

原始模式的改进

function Cat(name,color){
    return {
        name  : name,
        color : color
    }
}

然后生成实例对象,但其实就等于在调用函数,返回一个对象。

var cat1 = Cat('大毛','黄色');
var cat2 = Cat('二毛','黑色');

这种问题是,如果 Cat 中不进行额外的处理,这里仍旧不能反映出来 cat1 和 cat2 内在的联系。

构造函数模式

function Cat(name,color){
    this.name = name;
    this.color = color;
}

现在就可以生成实例对象了。

var cat1 = new Cat('大毛','黄色');
var cat2 = new Cat('二毛','黑色');

这时 cat1 和 cat2 会自动含有一个 constructor 属性,指向他们的构造函数。

console.log(cat1 instanceof Cat); //true
console.log(cat2 instanceof Cat); //true

但此时这种方式仍旧存在其固有的问题。假设这里有一个方法。

function Cat(name,color){
    this.name = name;
    this.color = color;
    this.type = '猫科动物';
    this.eat = function(){console.log('吃老鼠');}
}

生成实例

var cat = Cat('大毛','黄色');
console.log(cat.type); // 猫科动物
cat.eat(); // 吃老鼠。

表面上没什么问题,用的时候也很好用,但是假如我们需要生成大量的 Cat 的实例,那么每个实例都会有 type 属性和 eat 方法,而且这两个都是一摸一样的内容,导致了内存的浪费,因为此时两个实例的相同属性和方法,并不是同样的内存地址。会存在多份。

那么,能不能将所有的相同的属性和方法只存在一份,然后每个实例同样的也可以使用呢?答案肯定是可以的。

prototype 模式

function Cat(name,color){
    this.name = name;
    this.color = color;
}

Cat.prototype.type = '猫科动物';
cat.prototype.eat = function(){console.log('吃老鼠');}

这时候,

var cat1 = new Cat('大毛','黄色');
var cat2 = new Cat('二毛','黑色');

此时,再多的实例对象,其 type 属性和 eat 方法都指向的是同一份,也即相同的内存地址。

console.log(cat1.eat === cat2.eat); //true

prototype 模式的验证方法

为了配合 prototype 模式, javascript 定义了一些辅助方法,帮助我们更方便的使用它。

isPrototypeOf()

这个方法用来判断,某个 prototype 对象和某个实例之间的关系。

console.log(Cat.prototype.isPrototypeOf(cat1)); //true

hasOwnProperty()
次方法用来判断某个属性到底是prototype 对象的属性还是类定义的(本地属性)。

console.log(cat1.hasOwnProperty('name')); true
console.log(cat1.hasOwnProperty('type')); false

in 运算符

此运算符用来判断某个属性是不是存在于某个实例中,即某个实例是否包含某个属性,不管是本地还是继承自 prototype

console.log("name" in cat1); // true
console.log('type' in cat1); // true
console.log('typo' in cat1); // false

in 运算符还可以用来遍历对象的属性。

for(var key in cat1){
    console.log('cat1[' + key ' ]=' + cat1[key]);
}

当然在遍历的时候可以使用 hasOwnProperty() 来限制输出的属性。