安全的类型检测

typeof操作符

检测数据类型的结果可能会不正确;

instanceof操作符

操作符在多个全局作用域下存在问题:

var value = [];
var isArray = value instanceof Array;
console.log(isArray);

上述代码都在全局作用域,返回true;但如果value在其他frame中,则返回false。

JSON对象

该对象也难以确定是否为原生对象;

解决办法:

因为在任何值上调用Object原生的toString()方法,都返回一个[Object NativeConstructorName]格式的字符串。每个类在内部都有一个[[Class]]属性,这个属性指定了上述字符串中构造函数的函数名,如:

console.log(Object.prototype.toString()); //[object Object]
//利用call():
var value = [];
console.log(Object.prototype.toString.call(value)); //[object Array]
//进一步完善:
function whichType (value) {
    console.log(Object.prototype.toString.call(value));
}
whichType("Obj"); //[boject String]
whichType(["hello"]); //[object Array]
whichType(321); //[object Number]
//检测是否为函数:
function isFunction (value) {
    return Object.prototype.toString.call(value) == "[object Function]";
}
console.log(isFunction(Object.prototype.toString)); //true
//检测原生JSON对象:
console.log(window.JSON && Object.prototype.toString.call(JSON) == "[object JSON]");

不适用于IE中COM对象形式实现的函数

作用域安全的构造函数

当使用new调用构造函数时,构造函数内用到的this对象会指向新创建的对象实例,如:

function Person (name) {
    this.name = name;
}
var person = new Person("oliver");
console.log(person.name);

问题是当没有使用new操作符,直接调用构造函数,this会映射到全局对象window上,导致错误对象属性的意外增加:

function Person (name) {
    this.name = name;
}
var person = Person("oliver");
console.log(window.name); //oliver

解决办法是创建一个作用域安全的构造函数:

function Person(name) {
    if (this instanceof Person) { //如果this是Person的实例
        this.name = name;
    } else {
        return new Person(name); //否则调用new操作符
    }
}
var person1 = Person("oliver");
console.log(person1.name); //oliver
var person2 = new Person("troy");
console.log(person2.name); //troy
console.log(window.name); //""

但是,如果使用构造函数窃取模式的继承且不实用原型链,那么这个继承很可能被破坏如:

function Person(name) {
    if (this instanceof Person) { //如果this是Person的实例
        this.name = name;
    } else {
        return new Person(name); //否则调用new操作符
    }
}
function People (name,age) {
    Person.call(this, name);
    this.age = age;
}
var p = new People("Oliver", 18);
console.log(p.name); //undefined
console.log(p.age); //18

结合使用原型链或者寄生组合则可以解决这个问题:

function Person(name) {
    if (this instanceof Person) { //如果this是Person的实例
        this.name = name;
    } else {
        return new Person(name); //否则调用new操作符
    }
}
function People (name,age) {
    Person.call(this, name);
    this.age = age;
}
People.prototype = new Person(); //关键点
var p = new People("Oliver", 18);
console.log(p.name); //Oliver
console.log(p.age); //18

惰性载入函数

惰性函数就是函数执行的分支仅会发生一次。

第一种

就是在函数被调用时再处理函数:

function createXHR () {
    if (typeof XMLHttpRequest !== "undefined") {
        createXHR = function () { //关键点
            return new XMLHttpRequest();
        }
    } else if (typeof ActiveXObject !== "undefined") {
        createXHR = function () { //关键点
            return new ActiveXObject(["MSXML2.XMLHttp"]);
        }
    } else {
        createXHR = function () { //关键点
            throw new Error("No XHR object available.");
        }
    }
    return createXHR(); //关键点
}

第二种

就是指定适当的函数:

function createXHR () {
    if (typeof XMLHttpRequest !== "undefined") {
        return function () { //关键点
            return new XMLHttpRequest();
        }
    } else if (typeof ActiveXObject !== "undefined") {
        return function () { //关键点
            return new ActiveXObject(["MSXML2.XMLHttp"]);
        }
    } else {
        return function () { //关键点
            throw new Error("No XHR object available.");
        }
    }
    return createXHR(); //关键点
}

函数绑定

函数绑定要创建一个函数, 可以在特定的this环境中以指定参数调用另一个函数。 该技巧常常和回调函数与事件处理程序一起使用, 以便在将函数作为变量传递的同时保留代码的执行环境。 由于代码之中存在着this变量, 而this在当前环境下指向确定的对象, 但是当更改代码的执行环境时, 就会出现问题了。 为了解决这个问题, javascript函数库中实现了一个bind() 函数来解决这个问题。

一个简单的bind() 函数接收一个函数和一个环境, 并返回一个在给定环境中调用给定函数的函数, 并且将所有参数原封不动传递过去。 语法如下:

function bind(fn, context) {
    return function() {
        return fn.apply(context, arguments);
    }
}

注意这里使用的arguments并不是bind() 的, 是内部函数的。

var handler = {
    message: "Event handled",
    handleClick: function(event) {
        alert(this.message);
    }
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler));

ECMAScript5为所有函数定义了一个原生的bind() 方法, 进一步简化了操作。

var handler = {
    message: "Event handled",
    handleClick: function(event) {
        alert(this.message);
    }
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));

它们主要用于事件处理程序以及setTimeout() 和setInterval()。 然而被绑定函数与普通函数相比有更多的开销, 它们需要更多内存, 同时也因为多重函数调用稍微慢一些, 所以最好只在必要时使用。

函数柯里化

它用于创建已经设置好了一个或多个参数的函数。 函数柯里化的基本方法是: 使用一个闭包返回一个函数。 当函数被调用时, 返回的函数还需要设置一些传入的参数。

柯里化函数通常由以下步骤动态的创建: 调用另一个函数并为它传入要柯里化的函数和必要参数。 下面是创建柯里化函数的通用方式:

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);
    }
}

这种变化也需要额外的开销


JS菌
6.4k 声望2k 粉丝