22.1 高级函数
22.1.1 安全的类型检测
对于之前,我们要去判断一个变量的类型,对于基本类型的,可以使用typeof,对于引用类型的,我们可以使用 instanceof ,不过,这两种方法在某些情况下,是不可靠的,会存在一些兼容性问题。
接下来,我们介绍一种更佳可靠的检测方法:
我们知道,在任何值上调用 Object 原生的 toString() 方法,都会返回一个 [object NativeConstrectorName] 格式的字符串,每个类在内部都有一个 [[Class]] 属性,这个属性中就指定了上述字符串中的构造函数名。
比如,我们可以使用下面方法来检测各种对象:
function isArray(value){
return Object.prototype.toString.call(value) === "[object Array]";
}
function isFunction(value){
return Object.prototype.toString.call(value) === "[object Function]";
}
function isRegExp(value){
return Object.prototype.toString.call(value) === "[object RegExp]";
}
22.1.2 作用域安全的构造函数
构造函数其实就是一个使用 new 操作符调用的函数。当使用 new 调用时,构造函数内用到的this对象会指向新创建的对象实例。
我们来看一个使用构造函数的例子:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
}
var person = new Person('jack', 29, 'teacher');
person.name ==> 'jack'
person.age ==> 29
person.job ==> 'teacher'
可是!!!
这个模式会出现一个问题,在你有意或者无意漏掉 new 操作符时,会发生以下事情
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
}
var person = Person('jack', 29, 'teacher');
person.name ==> 报错
window.name ==> 'jack'
window.age ==> 29
window.job ==> 'teacher'
这是什么原因呢???
因为漏掉了 new 操作符后,Person()被作为普通函数调用,大家知道,普通函数内部的this指向了window,所以name,age,job
这三个属性会被挂载到window对象上,造成全局变量污染。
这一点,对于构造函数来说,是一个极大的问题,怎么样避免这个问题,可以更安全的使用自己的构造函数呢????
那就是:创建一个作用域安全的构造函数!!!!
function Person(myName, age, job){
if( this instanceof Person ){
this.myName = myName;
this.age = age;
this.job = job;
} else {
return new Person(myName, age, job);
}
}
var person = Person('jack', 29, 'teacher');
window.myName ==> undefined
person.myName ==> 'jack'
22.1.3 惰性载入函数
22.1.4 函数绑定
22.1.5 函数柯里化
22.2 防篡改对象
22.2.1 不可扩展对象
默认情况下,所有对象都是可扩展的。
比如:
var person = { name: 'jack' };
person.age = 20;
如何禁止给对象添加扩展呢??可以使用对象的 Object.preventExtensions()方法
var person = { name: 'jack' };
Object.preventExtensions(person);
person.age = 20;
console.log(person.age) ==》 undefined
还有,使用 Object.isExtensible() 可以检测对象是否可扩展
22.2.2 密封的对象
可以使用 Object.seal() 方法使一个对象成为密封对象。成为密封对象后,此对象将不可扩展,而且其属性不可删除,但是可以修改
var person = { name: 'jack' };
Object.seal(person);
delete person.name;
alert(person.name); ==> 'jack'
person.name = 'pony';
alert(person.name) ==> 'pony';
可以使用 Object.isSealed() 来检测对象是否被密封了。
22.2.3 冻结的对象
最严格的防篡改级别是冻结对象。冻结的对象,既不可扩展,又是密封,又不能修改其属性。
即 Object.freeze();
对于一个JS库而言,冻结对象是很有用的。因为可以极大的防止库中核心对象被修改。
22.3 高级定时器
定时器对队列的工作方式是:当特定事件过去后,将代码插入。注意,给队列添加代码并不意味着代码会立即执行,只能表示它会尽快执行。
等待浏览器线程空闲会执行。
使用 setInterval() 会存在以下问题:
(1) 某些间隔会被跳过
(2) 多个定时器的代码执行之间的间隔可能会比预期的小。
所以,推荐使用 setTimeout() 来代替 setInterval()
22.3.2 Yielding Processes
不同于桌面应用往往能够随意控制他们要的内存大小和处理器时间,JS被严格限制了,以防止恶意的web程序员把用户电脑搞挂了。
22.3.3 函数节流(重要概念)
在浏览器中,某些计算和处理要比其他的昂贵很多,其高频率的更改可能会让浏览器崩溃。为了绕开这个问题,你可以使用定时器对该函数进行节流。
函数节流背后的基本思想是指,某些代码不可以在没有间断的情况连续重复执行。
该模式的基本形式:
var processor = {
timeoutId: null,
//实际进行处理的方法
performProcessing: function(){
//实际执行的代码
},
//初始处理调用的方法
process: function(){
clearTimeout( this.timeoutId );
var that = this;
this.timeoutId = setTimeout(function(){
that.performProcessing();
},100);
}
}
//尝试开始执行
processor.process();
当调用了 process(),第一步是清除存好的 timeoutId,来阻止之前的调用被执行。然后创建一个新的定时器调用 performProcessing()。
可以将以上函数用下面这个函数来简化
function throttle(method, context){
clearTimeout( method.tId );
method.tId = setTimeout(function(){
method.call(context)
},100)
}
接下来,我们再来看一个例子:
window.onresize = function(){
var div = document.getElementById('myDiv');
div.style.height = div.offsetWidth + 'px';
}
不知道大家有没有意识到,上面这段代码可能会带来两个问题:
(1):首先,要计算 offsetWidth 属性,如果该元素或页面上其他元素有非常复杂的CSS样式,那么这个过程将会很复杂。
(2):其次,设置某个元素的高度需要对页面进行回流来令改动生效。如果页面有很多元素同时应用了相当数量的css,那么又会有很多计算。
节流优化后的代码:
function resizeDiv(){
var div = document.getElementById('myDiv');
div.style.height = div.offsetWidth + 'px';
}
window.onresize = function(){
throttle(resizeDiv);
}
总结,只要代码是周期性执行的,都应该使用截流,比如以下js事件:input、scroll、resize等。
如果大家有人用过 underscore.js,里面有关于函数节流(throttle)与函数去抖(debounce)两个方法,大家可以比较学习下