JavaScript高级技巧

作用域安全的构造函数

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("Nicholas", 29, "Software Engineer");
alert(window.name); //""
alert(person1.name); //"Nicholas"
var person2 = new Person("Shelby", 34, "Ergonomist");
alert(person2.name); //"Shelby"

主要是防止以下情况发生:

没有通过new来创建Person对象
var person = Person("Nicholas", 29, "Software Engineer");
alert(window.name); //"Nicholas"
alert(window.age); //29
alert(window.job); //"Software Engineer"

惰性载入函数

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

当函数中有过多的if else的时候,使用此方法可以在下次函数调用的时候不要去判断了,提高了效率。

函数绑定

在点击的时候需要弹出message

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

实际上弹出的是undefined,因为this指向的作用域是btn的运行环境 而不是handler

解决办法:通过闭包实现,让handler对象去调用 handleClick()

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

创建过多的闭包导致代码无法被调试,于是:

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

在 bind() 中创建了一个闭包,闭包使用 apply() 调用传入的函数,并给 apply() 传递 context 对象和参数。注意这里使用的 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));

ES5为每个函数提供了原生的bind方法

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

函数柯里化

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

简单版:

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() 函数的主要工作就是将被返回函数的参数进行排序。 curry() 的第一个参数是要进行柯里化的函数,其他参数是要传入的值。为了获取第一个参数之后的所有参数,在 arguments 对象上调用了 slice() 方法,并传入参数 1 表示被返回的数组包含从第二个参数开始的所有参数。然后 args 数组包含了来自外部函数的参数。在内部函数中,创建了 innerArgs 数组用来存放所有传入的参数(又一次用到了 slice() )。有了存放来自外部函数和内部函数的参数数组后,就可以使用 concat() 方法将它们组合为 finalArgs ,然后使用 apply() 将结果传递给该函数。注意这个函数并没有考虑到执行环境,所以调用 apply() 时第一个参数是 null 。

function add(num1, num2) {
    return num1 + num2;
}
var curriedAdd = curry(add, 5);
alert(curriedAdd(3)); //8
function add(num1, num2){
return num1 + num2;
}
var curriedAdd = curry(add, 5, 12);
alert(curriedAdd()); //17
//需要被柯理化的函数只接收两个参数,所以传多了无效

函数柯里化还常常作为函数绑定的一部分包含在其中,构造出更为复杂的 bind() 函数。例如:

function bind(fn, context) {
    var args = Array.prototype.slice.call(arguments, 2);
    return function() {
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return fn.apply(context, finalArgs);
    };
}

当你想除了 event 对象再额外给事件处理程序传递参数时,这非常有用,例如:

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

在这个更新过的例子中, handler.handleClick() 方法接受了两个参数:要处理的元素的名字和event 对象。作为第三个参数传递给 bind() 函数的名字,又被传递给了 handler.handleClick() ,handler.handleClick() 也会同时接收到 event 对象。这里有一个疑惑,event一般作为事件的第一个参数,这里第二个参数作为event,会接收到吗?

ECMAScript 5的 bind() 方法也实现函数 里化,只要在 this 的值之后再传入另一个参数即可。

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

高级定时器

JavaScript是单线程语言,事件处理程序,Ajax的回调函数都是在主线程中执行的,所以会有一个队列来管理这些程序的执行顺序,定时器中的执行代码是在间隔指定时间后添加到队列中的。

var btn = document.getElementById("my-btn");
btn.onclick = function(){
setTimeout(function(){
document.getElementById("message").style.visibility = "visible";
}, 250);
//其他代码
};

如果前面例子中的 onclick 事件处理程序执行了 300ms,那么定时器的代码至少要在定时器设置之后的 300ms后才会被执行。队列中所有的代码都要等到 JavaScript 进程空闲之后才能执行,而不管它们是如何添加到队列中的

这里写图片描述

重复的定时器

使用 setInterval() 创建的定时器确保了定时器代码规则地插入队列中。这个方式的问题在于,定时器代码可能在代码再次被添加到队列之前还没有完成执行,结果导致定时器代码连续运行好几次,而之间没有任何停顿。幸好,JavaScript 引擎够聪明,能避免这个问题。当使用 setInterval() 时,仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中

这种重复定时器的规则有两个问题:(1) 某些间隔会被跳过;(2) 多个定时器的代码执行之间的间隔可能会比预期的小。假设,某个 onclick 事件处理程序使用 setInterval() 设置了一个 200ms 间隔的重复定时器。 如果事件处理程序花了 300ms多一点的时间完成, 同时定时器代码也花了差不多的时间,就会同时出现跳过间隔且连续运行定时器代码的情况

这里写图片描述

这个例子中的第 1 个定时器是在 205ms 处添加到队列中的, 但是直到过了 300ms 处才能够执行。 当执行这个定时器代码时,在 405ms处又给队列添加了另外一个副本。在下一个间隔,即 605ms 处,第一个定时器代码仍在运行,同时在队列中已经有了一个定时器代码的实例。结果是,在这个时间点上的定时器代码不会被添加到队列中。结果在 5ms处添加的定时器代码结束之后,405ms 处添加的定时器代码就立刻执行。

为了避免这个问题,开始使用setTimeout链式调用:

setTimeout(function(){
//处理中
setTimeout(arguments.callee, interval);
}, interval);

demo:将一个div元素右移到200停止

setTimeout(function() {
    var div = document.getElementById("myDiv");
    left = parseInt(div.style.left) + 5;
    div.style.left = left + "px";
    if (left < 200) {
        setTimeout(arguments.callee, 50);
    }
}, 50);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值