1、安全类型检测
问题背景:
typeof和instanceof有时候不靠谱,比如Safari(一直到4)在对正则表达式使用typeof时会返回“function”,instanceof在多个全局作用域存在的情况下(比如一个页面多个frame),也会出现问题,比如:
var isArray = value instanceof Array;
所以引入了安全的类型检测概念。
实现:
基本原理是使用toString方法,在任何值上调用Object的toString方法,都会返回一个[object NativeConstructorName]格式的字符串。
function isArray(value){
console.log(Object.prototype.toString.call(value));//[object Array]
return Object.prototype.toString.call(value) == "[object Array]";
}
console.log(isArray([]));//true
可以看到,Object.prototype.toString.call(value)返回了一段[object Array](此处Object.prototype也可以写成{}),利用这种方法可以构建一个安全的类型检测函数。
同理可以写出对Function、RegExp类型对象的检测
function isFunction(value){
return {}.toString.call(value) == "[object Function]";
}
function isRegExp(value){
return {}.toString.call(value) == "[object RegExp]";
}
console.log(isFunction(()=>{}));//true
console.log(isRegExp(/1/));//true
如果要检测非原生的类型,比如JSON
function isJSON(value){
return window.JSON&&{}.toString.call(value) == "[object JSON]";
}
注意:这是在原生toString方法的条件下进行的。
2、作用域安全的构造函数
问题背景:
当不使用new操作符调用构造函数时,由于this对象的晚绑定,会导致构造函数中用this声明的变量暴露到全局作用域上。
function Person(name,job,age){
this.name = name;
this.job = job;
this.age = age;
}
var p = Person("周子越","学生","20");
//console.log(p.age);//Uncaught TypeError: Cannot read property 'age' of undefined
console.log(age);//20
实现:
对this对象进行处理,当this不是构造函数的类型时,返回使用new操作符调用构造函数的返回值。
function Person(name,job,age){
if(this instanceof Person){
this.name = name;
this.job = job;
this.age = age;
}else{
return new Person(name,job,age);
}
}
var p = Person("周子越","学生","20");
console.log(p.age);//20
// console.log(age);//Uncaught ReferenceError: age is not defined
3、惰性载入函数
问题背景:
由于浏览器内部的差异,我们常常需要做客户端检测,使用if分支来对应相应的代码段,这样每次执行时都需要走一边if的判断,我们希望只走一次if判断,此时可以使用惰性载入函数的方法。
实现:
因为代码太长了,所以这里只给了相关思路。思想就是在检测后直接使用一个函数覆盖原来的函数。
第一种实现,函数声明
var flag = "a";
var a = 0;
var b = 0;
function lazyLoadFunc(){
if(flag == "a"){
lazyLoadFunc = function(){
a++;
}
}else{
lazyLoadFunc = function(){
b++;
}
}
lazyLoadFunc();
}
lazyLoadFunc();
console.log(lazyLoadFunc)// function(){ a++; }
lazyLoadFunc();
console.log(a);//2
console.log(b);//0
第二种实现,函数表达式
var flag = "a";
var a = 0;
var b = 0;
var lazyLoadFuuc = (function(){
if(flag == "a"){
lazyLoadFunc = function(){
a++;
}
}else{
lazyLoadFunc = function(){
b++;
}
}
})();
lazyLoadFunc();
console.log(lazyLoadFunc)// function(){ a++; }
lazyLoadFunc();
console.log(a);//2
console.log(b);//0
4、函数绑定
好像就是使用apply实现bind的原理。
function bind(fn,context){
return function(){
return fn.apply(context,arguments);
}
}
var obj = {
value:"local",
speak:function(){
console.log(value)
}
}
var value = "global";
var newFunc = bind(obj.speak,this);
newFunc();//global
5、函数柯里化
这一块没有太懂,给一个简单示例
function curry(func){
var args = [].slice.call(arguments,1);
return function(){
var innerArgs = [].slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return func.apply(null,finalArgs);
}
}
function add(num1,num2){
return num1 + num2;
}
var curriedAdd = curry(add,5);
console.log(curriedAdd(5));//10
我是这样理解的:curry函数可以返回一个预先设置好参数的函数
6、防篡改对象
问题背景
在js中,所有的对象都可以被在同意环境中运行的代码修改,es5提供了一组方法来防止开发者不小心篡改一些重要的对象。
注意:一旦把对象定义为放篡改,就无法撤销了。
补充知识点:
每一个对象都有着自己的“特性”,[[Writable]]、[[Enumerable]]、[[Value]]、[[Configurable]]、[[Get]]、[[Set]]
不可扩展对象:
不可以为对象添加新的属性、方法,修改的是;
非严格模式下可以使用obj.a语句,严格模式下会报错
var obj = {}
console.log(Object.isExtensible(obj));//true
Object.preventExtensions(obj);
console.log(Object.isExtensible(obj));//false
obj.a = 0;
console.log(obj.a);//undefined
密封对象:
不可扩展,且不可以删除对象的属性或者方法
Object.seal(obj);
Object.isSealed(obj);
冻结对象:
密封,且不可以更改属性和方法值
Object.freeze(obj);
Object.isFrozen(obj);
7、高级定时器
js是运行于单线程环境中的,而定时器仅仅只是计划代码在未来的某个时间执行,执行时机是不能保证的。定时器的工作方式是,当特定时间过去后将代码插入到代码队列(宏任务队列?)中,并且添加到队列中并不意味着能立刻执行,只能表明将尽快执行。
常用的就是setTimeout(callback,timeout);setInteval(callback,interval);
问题背景:
setInterval只能确保定时器代码规则地插入队列中,首先面对一个问题:定时器代码可能在代码再次被添加到队列之前还没有完成执行,导致定时器代码连续运行多次而之间没有停顿,这个问题被js引擎解决了,当没有该定时器地任何代码实例时,才将定时器代码加入队列中。
但依然存在一些问题:因为定时器代码未执行,某些间隔可能会被跳过;多个定时器地代码执行之间的间隔可能会比预期的小。
因此我们需要链式使用setTimeout实现一个更好的setInterval(保证间隔不被跳过)
实现:
function setInterval(callback,interval){
setTimeout(function(){
callback();
setTimeout(arguments.callee,interval);
},interval);
}
**补充知识:**callee返回正在执行的函数本身的引用。callee是arguments的一个属性,这个属性是一个指针,指向这个拥有arguments对象的函数
8、Yielding Processes
**问题背景:**运行在浏览器中的js都被分配了一个确定数量的资源(这点不同于桌面应用),这是为了防止恶意的web程序员把用户的计算机搞挂了,比如长时间循环,此时浏览器就会询问用户是否要执行,作为开发人员应该避免这样的情况。
当一个任务不必同步完成且不必按照顺序完成时,可以使用分块的和定时器结合的方法来执行,这就是数组分块(array chunking)技术。
实现:
function chunk(array,process,context,timeout){
setTimeout(function(){
var item = array.shift();
process.call(context,item);
if(array.length>0){
setTimeout(arguments.callee,timeout);
}
},timeout);
}
chunk([1,2,3,4,5,6,7,8,9],function(value){
console.log(value);
},this,100);
注意:因为数组是一个对象,所以会直接修改传入的数组的值,如果希望不更改数组,可以传入数组的引用,关于获得数组的引用有个技巧
array.concat();
9、函数节流
问题背景:
高频率操纵DOM会使浏览器崩溃,比如为onresize时间绑定一个DOM操作时,如果采用拖动鼠标更改浏览器大小的话,会导致这个事件不断被触发,最终浏览器崩溃。因此需要采取一定的措施。
注意:这里似乎红宝书和网上的资料不一样,红宝书认为节流是在规定时间内连续触发则清楚定时器,这点网上资料会称作防抖
关于防抖与节流可以参考这篇文章
红宝书给的实现:
function throttle(method,context,timeout){
clearTimeout(method.tId);
method.tId = setTimeout(function(){
method.call(context);
},timeout);
}