《JavaScript高级程序设计》读书笔记22.1高级函数

1. 安全的类型检测

在任何值上调用Object类型原生的toString()方法,都会返回一个[Object NativeConstructorName]格式的字符串。且每个我认为是引用类型)在内部都有一个[[class]]属性,这个属性中就指定了上述字符串中构造函数名。

1
2
3
function isArray(value){
return Object.prototype.toString.call(value)=='[object Array]';
}
1
2
3
4
5
6
7
function is(type, obj) {
var clas = Object.prototype.toString.call(obj).slice(8, -1);
return obj !== undefined && obj !== null && clas === type;
}

is('String', 'test'); // true
is('String', new String('test')); // true

注意: Object类型原生的toString()方法只能检测原生构造函数的构造函数名。因此,开发人员定义的任何构造函数都返回[object Object]。

2. 作用域安全的构造函数

问题:构造函数当普通函数使用时,函数内的this对象会被绑定window对象(全局执行环境==全局作用域),从而可能会导致页面上出现错误,因此作用域不安全。
解决方法:首先确认this对象是正确类型的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name,age,job){
if(this instanceof Person){
this.name=name;
this.age=age;
this.job=job;
}else{
return new Person(name,age,job);
}
}

var person1=Person('li',29,'software engineer');
alert(window.name); //''
alert(person1.name); //'li'

后遗症:使用作用域安全的构造函数,就锁定了可以调用构造函数的环境。也就是说其作用域只有自己的变量对象和全局变量对象,不能被其他函数借用。使用借用构造函数模式继承,那么这个继承可能被破坏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Polygon(sides){
if (this instanceof Polygon) {
this.sides = sides;
this.getArea = function(){
return 0;
};
} else {
return new Polygon(sides);
}
}

function Rectangle(width, height){
Polygon.call(this, 2);
this.width = width;
this.height = height;
this.getArea = function(){
return this.width * this.height;
};
}

var rect = new Rectangle(5, 10);
alert(rect.sides); //undefined

//这个实例应该继承Polygon的sides属性,实际上没有

解释:由于Polygon作用域是安全的,this对象不是Polygon的实例,所以会创建一个新Polygon对象。Rectangle构造函数中是this对象并没有得到增长,同时Polygon.call()返回的值也没有用到,所以Recanglt实例中不会有sides属性。
解决后遗症方法借用构造模式结合使用原型链 组合继承或者寄生组合式继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Polygon(sides){
if (this instanceof Polygon) {
this.sides = sides;
this.getArea = function(){
return 0;
};
} else {
return new Polygon(sides);
}
}

function Rectangle(width, height){
Polygon.call(this, 2); //第二次调用Polygon()
this.width = width;
this.height = height;
this.getArea = function(){
return this.width * this.height;
};
}

Rectangle.prototype = new Polygon(); //增加这一行,就成为组合继承模式。第一次调用Polygon()

var rect = new Rectangle(5, 10);
alert(rect.sides); //2

解释: 第一次调用时,在Rectangle原型上添加了sides属性,第二次调用时,this代表的rect实例的instanceof指向Polygon,所以Polygon.call()会照原意执行。

3. 惰性载入函数

惰性载入: 表示函数执行的分支仅会发生一次。两种实现惰性载入的方式:一是在函数被调用时再处理函数二是声明函数时就指定恰当的函数。

  1. 在函数被调用时再处理函数:在第一次调用的过程中,该函数会被覆盖为另一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支了。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    function createXHR(){
    if (typeof XMLHttpRequest != "undefined"){
    createXHR = function(){ //新增按合适方式执行的函数
    return new XMLHttpRequest();
    };
    } else if (typeof ActiveXObject != "undefined"){
    createXHR = function(){ //新增按合适方式执行的函数
    if (typeof arguments.callee.activeXString != "string"){
    var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],
    i, len;

    for (i=0,len=versions.length; i < len; i++){
    try {
    new ActiveXObject(versions[i]);
    arguments.callee.activeXString = versions[i];
    break;
    } catch (ex){
    //skip
    }
    }
    }
    return new ActiveXObject(arguments.callee.activeXString);
    };
    } else {
    createXHR = function(){
    throw new Error("No XHR object available.");
    };
    }
    return createXHR(); //新增按合适方式执行的函数
    }

代码解释:这个惰性载入的createXHR()中,if语句的每一个分支都会为createXHR变量赋值,有效覆盖了原有的函数。最后一步便是调用新赋的函数。下一次调用createXHR()时,会直接调用被分配的函数。

  1. 在声明函数时就指定适当的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var createXHR = (function(){
if (typeof XMLHttpRequest != "undefined"){
return function(){
return new XMLHttpRequest();
};
} else if (typeof ActiveXObject != "undefined"){
return function(){
if (typeof arguments.callee.activeXString != "string"){
var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],
i, len;
for (i=0,len=versions.length; i < len; i++){
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex){
//skip
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
};
} else {
return function(){
throw new Error("No XHR object available.");
};
}
})();

4. 函数绑定

函数绑定: 要创建一个函数,可以在特定的this环境中以指定参数调用另一个函数。
用处: 和回调函数、事件处理程序、setTimeout()、setInterval()一起使用,以便在将函数作为变量传递的同时保留代码执行环境。
函数bind():接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动传递过去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function bind(fn,context){
return function(){
return fn.apply(context,arguments);
};
}

//使用
var handler = {
message: "Event handled",
handleClick: function(event){
alert(this.message + ":" + event.type);
}
};

var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler));
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));

ECMA5方法原生bind(),IE9+、FireFox 4+和Chrome。

5. 函数柯里化

用处: 用于创建已经设置好了一个或多个参数的函数。
方法: 使用闭包返回一个函数。
创建步骤: 调用另一个函数并为它传入要柯里化的函数和必要参数。

1
2
3
4
5
6
7
8
function curry(fn){          //不止一个参数
var args=Array.prototype.slice.call(arguments,1);
return function(){
var innerArgs=Array.prototype.slice.call(arguments);
var finalArgs=args.concat(innerArgs);
return fn.apply(null,finalArgs);
};
}

curry() 工作目的:将被返回函数的参数进行排序。

1
2
3
4
5
6
7
8
9
10
function add(num1,num2){
return num1+num2;
}

var curriedAdd=curry(add,5);
alert(curriedAdd(3)); //8


var curriedAdd=curry(add,512);
alert(curriedAdd()); //17

更复杂的bind()函数: 函数柯里化作为函数绑定一部分

1
2
3
4
5
6
7
8
function bind(fn,context){     //不止两个参数
var args=Array.prototype.slice.call(arguments,2);
return function(){
var innerArgs=Array.prototype.slice.call(arguments);
var finalArgs=args.contact(innerArgs);
return fn.apply(context,finalArgs);
}
}

ECMA5的bind()方法也实现函数柯里化,再传入另一个参数即可。

liborn wechat
欢迎您扫一扫上面的微信二维码,订阅我的公众号!