微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

JS高程-高级技巧阅读摘要

一种安全的类型检测方法

arr instanceOf Array 返回true的条件是arr必须是一个数组,且与Array构造函数在同一个全局作用域内,
若arr是在另一个iframe中定义的数组,就会返回false
因此,一种安全的检测数组,函数,正则的方法是利用每个对象的toString方法:
function isArray(arr){
    return Object.prototype.toString.call(arr) == "[object Array]";
}
function isFunction(fn){
    return Object.prototype.toString.call(fn) == "[object Function]";
}
function isRegExp(reg){
    return Object.prototype.toString.call(reg) == "[object RegExp]";
}
但是对于开发人员自定义的对象类型,toString只返回[object Object]

构造函数中this的安全指向问题

如有以下一个函数

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
}

如何通过构造函数的方式调用函数,不会出现问题,但是当以普通函数的方式调用函数时,就有大trouble了:

var p = Person('a',12,'fe');

该语句会在window中执行,运行的结果便是直接在window对象上追加了三个属性,这就导致了潜在的风险(如name属性是window固有的)
其实问题产生的原因是this的晚绑定造成的,改进方法

function Person(name,job){
    if(this instanceOf Person){
        this.name = name;
        this.age = age;
        this.job = job;        
    } else {
        return new Person(name,job);
    }
}

通过这样的方式,就能保证永远通过构造函数的方式调用Person函数了。

惰性函数

在创建xhr对象的函数中,总会判断XMLHttpRequest和ActiveXObject这两个对象,if else语句不可少,但是任何判断都比没有判断耗时,如何减少if else判断语句呢?
解决的办法就是 惰性载入,而实现惰性载入函数的方式有两种,一种是在函数首次执行的时候,在函数内部根据判断改写函数,另一种是在函数创建的时候,根据判断创建出合适的函数

function createXHR(){
    if (window.XMLHttpRequest){
      return new XMLHttpRequest();
    }else if (window.ActiveXObject){
    // IE5,IE6
       return new ActiveXObject("Microsoft.XMLHTTP");
    }
}

第一种实现手段(首次执行依然需要判断)

function createXHR(){
    if (window.XMLHttpRequest){
        createXHR = function(){
          return new XMLHttpRequest();
        }
    }else if (window.ActiveXObject){
        // IE5,IE6
        createXHR = function(){
          return new ActiveXObject("Microsoft.XMLHTTP");
        }
    }
}

第二种实现手段(首次执行也不必判断,但会在代码首次加载时损失点性能

var createXHR = (function(){
    if (window.XMLHttpRequest){
      return function(){
          return new XMLHttpRequest();
    }
    }else if (window.ActiveXObject){
    // IE5,IE6
        return function(){
          return new ActiveXObject("Microsoft.XMLHTTP");
        }
    }
})();

函数绑定

函数绑定要创建一个函数,可以在特定的this环境中以指定参数调用一个函数。该技巧经常和回掉函数与实践处理程序一起使用,以便将函数作为函数参数的同时保存代码的执行环境。案例:

var handler = {
    message: "Event handled",handleClick:function(e){
        alert(this.message);
    }
}
var btn = $("#btn");
btn.addEventListener('click',handler.handleClick);//因为handler.handleClick()的执行环境是DOM(IE8指向window),所以结果是undefined

所以可以使用闭包来解决此问题:

btn.addEventListener('click',function(e){
    handler.handleClick(e);
});

将此抽象出来,得出一个可以将函数绑定到指定环境的函数,一般被称作bind函数

function bind(fn,context){
    return function(){
        return fn.apply(context,arguments);//apply的用武之地!!!
    }
}

至此,最初的那个事件绑定语句,就可以改写为:

btn.addEventListener('click',bind(handler.handleClick,handler));

目前,ES5已经为所有函数定义了一个原生的bind方法,进一步简化了操作。用法为(IE9+):

fn.bind(context);    //只是固定了执行环境
fn.bind(context)();    //执行!    想传个参怎么办?看下一节~

Tip: 被绑定的函数与普通函数相比有更多的开销,她们需要更多的内存,同时也因为多重函数调用稍慢一点。

函数柯里化

函数柯里化的作用是为需要多个参数的函数事先设置好几个参数,待到使用时只需传给接下来的几个参数即可。他与函数绑定是一样的:使用一个闭包返回一个函数,二者的区别在于,当函数调用时,返回的函数还需要设置一些传入的参数。

function sum(a,b){
    return a+b;
}
function curriedAdd(2,b){
    return 2+b;
}

所以现在考虑如何实现一个通用的curry函数,使其能curry所有函数。答案如下:

function curry(fn){
    var outerargs = Array.prototype.slice.call(arguments,1);//获取curry的第二个及以后的参数(作为fn的前几个参数)
    return function(){//抛出一个函数,这个函数是闭包,包含着对outerargs的引用。
        var innerArgs = Array.prototype.slice.call(arguments);//获取被抛出的函数的参数,作为fn的随后几个参数
        var finalArgs = outerargs.concat(innerArgs);//汇总fn的所有参数
        return fn.apply(null,finalArgs);//调用fn
    }
}
//使用姿势
function add(a,b){
    return a+b;
}
var curriedAdd = curry(add,2);
curriedAdd(5);//7

更高级的,在绑定函数中,若结合了这种技术,就能向绑定函数传参了:

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

据说很实用。ES5也同样实现了该方法(不是curry方法),只需继续向bind函数传参即可:

btn.addEventListener('click',handler.handleClick.bind(handler,"myArg"));

JS中的柯里化和函数绑定提供了强大的动态函数创建功能,使用bind还是curry要依据是否需要object对象响应来决定。

ES5中的防篡改对象(都是在ES5种增加的)

不可扩展对象

Object.preventExtensions(person);可以使person不能在定义后追加属性方法。
Object.isExtensible(person);判断能否扩展

密封对象

Object.seal(person);可以使对象不可拓展,并且不能删除已有的属性方法修改还是可以的)
Object.isSealed(person);判断是否密封

冻结对象

Object.freeze(person);密封对象,而且已有属性不能修改!
Object.isFrozen(person);判断是否冻结

定时器的高级玩法

记不住这句话,就丢大人了:定时器指定的时间间隔,表示何时将定时器的代码添加到队列,而不是何时执行代码
setInterval()定时器存在两个问题: 1 某些间隔会被跳过;2 多个定时器的代码执行之间的间隔可能会比预期小;

为了避免这两个缺点,可以使用链式setTimeout的方式:

setTimeout(function(){
    //处理中
    //启动下次定时
    setTimeout(arguments.callee,interval);
},interval)

这样做的好处是:在前一个定时器代码执行完之前,不会像队列插入新的定时器代码,确保不会有任何损失的间隔。而且可以保证在下一次定时器代码执行之前,至少要等待执行的间隔,避免了连续的运行。

数组分块技术

对于下面的函数,如果process函数特别耗时又连续被执行多次,那么就有可能遭到浏览器的阻止。

for(var i=0;i

因此,需要一种技术,让process函数多次调用的间隔久一点,绕过浏览器的监管。chunk应运而生:

function chunk(array,process,context){
    setTimeout(function(){
        var item = array.shift();
        process.call(context,item);
    if(array.length > 0){
        setTimeout(arguments.callee,100)
    }
},100);

}

函数接受三个参数:要处理的数据数组,处理函数,执行环境。内部启用了一个定时器,100ms(书中说这个时间适合多数情景)后用process处理一个data元素,处理完后再启动下一个定时器处理下一个元素,直到全部数据都被处理完。

函数节流

此处有错误,这里的函数其实实现的是去抖!!!!!!
节流:每隔指定的时间才执行一次函数, 去抖:停止某个事件后才执行一次函数
比如窗口的resize事件,用户不停的改变窗口大小,这时候我们期望每隔200ms执行一次函数,而不是用户停止改变后再执行一次函数

一个事件如果在短时间内被触发很多次,事件处理函数其实不应该一直被执行,一是可能没意义,二是当处理函数很耗性能时,体验不好。比如典型的浏览器窗口的onresize事件。所以诞生了函数节流这一概念: throttle

function throttle(method,context){//method是真正的处理函数
    clearTimeout(method.tId);//首次执行是没有tId的,以后如果多次触发名义上的处理函数,那么就立即清除真正处理函数的执行,并在100ms后执行真正的处理函数。
    method.tId = setTimeout(function(){
        method.call(context);
    },100)
}

但是事件该触发还是要触发的,即throttle一直被执行,但throttle屏蔽并控制了method的执行。

原文地址:https://www.jb51.cc/wenti/421934.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐