【JS红宝书学习】22高级技巧

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

10、自定义事件

11、拖放

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值