美文网首页
JavaScript 高级程序设计精要(22)

JavaScript 高级程序设计精要(22)

作者: 守护银河的河童 | 来源:发表于2019-03-25 17:42 被阅读0次

    准备把最近阅读过的JavaScript 高级程序设计分享出来,可以为大家节省时间,也用来做自己的备忘。如果有不懂或者错误的地方,欢迎留言或者私信。

    1 高级技巧

    • 使用高级函数
    • 防篡改对象
    • Yielding Timers
    1.1 安全的类型检测

    在web开发中能够区分原生与非原生JavaScript对象非常难哟,只有这样才能确切知道某个对象到底有哪些功能。

    typeof
    由于它一些无法与指导额行为,经常会导致检测数据得到不靠谱的结果。


    instanceof
    对于一个网页,或者一个全局作用域而言,使用instanceof操作符就能得到满意的结果 。它的问题在于假定单一的全局执行环境,如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的Array构造函数。

    if(value instanceof Array) {
     //对数组执行某些操作
    }
    

    instanceof操作符存在多个全局作用域的情况下(例如包含多个iframe)


    Array.isArray()
    这个方法的目的是最终确定某个值到底是不是数组,而不管它是在哪个全局执行环境中创建的。

    if(Array.isArray(value)){
      //对数组执行某些操作
    }
    

    检测某个对象到底是原生对象还是开发人员自定义的对象。

    浏览器开始原生支持JSON对象,因为很多人一直子啊使用Douglas Crockford的JSON库,而该库定义了一个全局JSON对象。于是开发人员很难确定页面中的JSON对象到底是不是原生的。

    alert(Object.prototype.soString.call(value)) //[object Array]
    

    由于原生数组的构造函数名与全局作用域无关,因此使用toString()就能保证返回一致的值。

    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]'; //检测正则表达式
    }
    

    不过要注意,对于在IE中以COM对象形式实现的任何函数,isFunction () 都将返回false(因为它们并非原生的JavaScript函数)


    1.2 作用域安全的构造函数

    多人在同一个页面上写JavaScript代码环境中,作用域安全构造函数就很有用。

    function Person(name, age, job) {
      this.name = name;
      this.age = age;
      this.job = job;
    }
    
    var person = new Person('Nicholas', 29,'Software Engineer');
    console.log (person.name) //Nicholas
    var otherPerson = Person('Nicholas', 29,'Software Engineer');
    console.log (window.name) //Nicholas
    console.log (otherPerson.name) //报错 
    

    当和new操作符连用时,则会创建一个新的Person对象,同事会给它分配这些属性,当没有new操作符来调用该构造函数的情况时,由于该this对象是在运行时绑定的,所以直接调用Person(),this会映射到全局对象window上,导致错误对象的意外增加。

    作用域安全的构造函数在进行任何更改前,首先确认this对象是正确类型的实例。如果不是,那么会创建新的实例并返回。

    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');
    console.log(window.name); //''
    console.log(person1.name) //Nicholas
    
    var person2 = new Person('Shelby', 34, 'Ergonomist');
    console.log(person2.name) //Shelby
    

    调用Person构造函数时无论是否使用new操作符,都会返回一个Person 的新实例,这就避免了在全局独享上意外设置属性。

    关于作用域安全的构造函数的贴心提示。实现这个模式后,你就锁定了可以使用构造函数的环境。

        function Polygon (sides) {
            if(this instanceof Polygon){
                this.sides = sides;
                this.getArea = function () {
                    return 0;
                }
            } else {
                return new Polygon(sides);
            }
        }
        function Rectangle(width,height){
            Polygon.call(this,2); //this 调用 Polygon的方法
            this.width = width;
            this.height = height;
            this.getArea = function () {
                return this.width * this.height
            }
        }
        var rect = new Rectangle (5, 10)
        console.log(rect.sides) //undefined
    

    这段代码里,Polygon构造函数是作用域安全的,然而Rectangle构造函数不是,新创建一个Rectangle实例之后,这个实例应该通过Polygon.call() 来继承Polygon的sides属性。

    由于Polygon返回的是一个Polygon的实例,Rectangle构造函数中的this对象并没有得到增长,同时Polygon.call() 返回的值也没有用到,所以Rectangle实例中就不会有sides属性。

        function Polygon (sides) {
            if(this instanceof Polygon){
                this.sides = sides;
                this.getArea = function () {
                    return 0;
                }
            } else {
                return new Polygon(sides);
            }
        }
    
        function Rectangle(width,height){
            Polygon.call(this,2); //this 调用 Polygon的方法
            this.width = width;
            this.height = height;
            this.getArea = function () {
                return this.width * this.height
            }
        }
        Rectangle.prototype = new Polygon();
        var rect = new Rectangle (5, 10)
        console.log(rect.sides) //2
    

    Rectangle实例同时也是一个Polygon实例,所以 Polygon.call会照原意执行,最终为Rectangle实例添加sides属性


    1.3 惰性载入函数

    因为浏览器之间行为的差异,多数JavaScript 代码包含了大量的 if 语句,将执行引导到正确的代码中

        let num = 0
        function createXHR(){
            console.log('调用==>', num ++) // 打印2次
            if (typeof XMLHttpRequest != "undefined"){
                return new XMLHttpRequest();
            } else if (typeof ActiveXObject != "undefined"){
                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){
    
                        }
                    }
                }
                return new ActiveXObject(arguments.callee.activeXString);
            } else {
                throw new Error("No XHR object available.");
            }
        }
        createXHR()
        setTimeout(
            () => {
                    createXHR()
                }, 2000
        )
    

    每次调用createXHR()的时候,它都要对浏览器所支持的能力仔细检查,首先检查内置的XHR,然后测试有没有基于ActiveX的XHR,最后如果都没有发现的话,就抛出一个错误。

    如果if语句不必每次执行,代码可以运行地更快一些,解决方案就是称之为惰性载入的技巧。

    有2种实现惰性载入的方式
    1、第一种就是在函数被调用时再处理函数。

        let num = 0
        function createXHR(){
            console.log('调用 ===>',num++); //打印一次
            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){
    
                            }
                        }
                    }
                    return new ActiveXObject(arguments.callee.activeXString)
                }   
            } else {
                createXHR = function () {
                    throw new Error("No XHR object available.");
                }     
            }
            console.log(createXHR);//function (){return new XMLHttpRequest()}
            return createXHR()
        }
        createXHR()
        setTimeout(
            () => {
                    createXHR()
                }, 2000
        )
    

    2、第二种就是在声明函数时就指定适当的函数。

        let num = 0;
        var createXHR = (function () {
                console.log(num++);
                if (typeof XMLHttpRequest !== 'undefined'){
                    return function () {
                        return new XMLHttpRequest();
                    };
                }else if (typeof ActiveXObject != 'undefined') {
                    return 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 {
                    return function () {
                        throw new Error(
                            'No XHRobject available'
                        )
                    }
                }
            })()
        createXHR()
        setTimeout(
            () => {
                    createXHR()
                }, 2000
        )
    

    第二个例子,技巧是创建一个匿名、自执行的函数,用于确定应该使用哪一个函数实现。


    1.4函数绑定

    函数绑定要创建一个函数,可以在特定的this 环境中以指定参数调用另一个函数。

    bind

    一个简单的bind()函数接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动传递过去。
    只要是将某个函数指针以值的形式进行传递,同时该函数必须在特定环境中执行,被绑定函数的效用就凸显出来了。

    然而,被绑定函数与普通函数相比有更多开销,他们需要更多内存。同时也因为多重函数调用稍微慢一点,所以最好只在必要时使用。


    1.4函数柯里化

    所谓"柯里化",就是把一个多参数的函数,转化为单参数函数

    用于创建已经设置好了一个或多个参数的函数。函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。
    区别在于:当函数被调用时,返回的函数还需要设置一些传入的参数。

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

        function add(num1, num2){
            return num1 + num2
        }
        function curriedAdd(num2){
            return add (5, num2)
        }
        alert(add(2,3))
        alert(curriedAdd(3)) //与柯里化有异曲同工之妙
    
        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)
            }
        }
    

    bind()函数提供了强大的动态函数创建功能。


    2 防篡改对象

    可以手工设置每个属性的[ [ Configurable ] ]、[ [ Writable ] ]、[ [ Enumerable ] ]、[ [ Value ] ]、[ [ Get ] ]、以及[ [ Set ] ]

    不过注意,一旦把对象定义为防篡改,就无法撤销了。

    2.1 不可拓展对象

    默认情况下,所有对象都是可以扩展的。
    Object.preventExtensions(obj)

        var person = { name : 'Nicholas' }
        Object.preventExtensions(person)
    
        person.age = 29;
        person.name = 'cher'
        console.log( person.age ) // undefined 调用了Object.preventExtensions()方法后,就不能给person对象添加新属性和方法了。在非严格模式下,给对象添加新成员会导致静默失败,即为undefined,严格模式下,会报错。
        console.log( person.name ) // cher 虽然不能给对象添加新成员,但是已有的成员不受影响,仍然可以修改和删除已有的成员。
    
    

    Object.isExtensible(obj)
    可确定对象是否可以扩展


    2.2 密封的对象 sealed object

    密封的对象不可扩展,而且已有成员[ [ Configurable ] ] 特性将被设置为false,这就意味着不能删除属性和方法。
    Object.seal()

        var person = { name : 'Nicholas' }
        Object.seal(person);
        person.age = 29;
        alert(person.age); //undefined
        delete person.name;
        alert(person.name); //Nicholas
    

    Object.isSealed()
    可以确定独享是否被密封


    2.3 冻结的对象frozen object

    冻结的对象既不可扩展,又是密封的,而且对象数据属性的[[ Writable ]]特性会被设置为false。
    如果定义[[ Set ]]函数,访问器属性仍然是可写的。
    Object.freeze()
    Object.isFrozen()

    对于JavaScript库的作者而言,冻结对象是很有用的(封装),因为怕使用者更改库核心代码。


    3 高级定时器 setTimeout setInterval

    JavaScript是运行与单线程的环境中的。定时器仅仅只是计划代码在未来的某个时间执行。执行时机是不能保证的。
    在JavaScript中没有任何代码是立刻执行的,但一旦进程空闲则尽快执行。
    setInterval()创建的定时器确保了定时器代码规则的插入队列中。定时器代码可能在代码再次被添加到队列之前还没有完成执行。当使用setInterval()时,仅当没有该定时器的任何其他代码实例时,才将定时器加入到队列中的最小时间间隔为指定间隔。

    3.1函数防抖
    某些代码不可以在没有间断的情况连续重复执行。
    在window.resize时建议使用。

        function resiveDiv() {
            console.log('resiveDiv调用了')
            var div = document.getElementById('myDiv');
            div.style.height = div.offsetWidth + 'px'
        }
    
        function debounce(method, context) {     
            clearTimeout(method.tId);     
            method.tId= setTimeout(function(){         
                method.call(context);     
                }, 100);
        }
        window.onresize = function () {
            // debounce(resiveDiv)
            resiveDiv() //即时调用了
        }
    
        function resiveDiv() {
            console.log('resiveDiv调用了')
            var div = document.getElementById('myDiv');
            div.style.height = div.offsetWidth + 'px'
        }
    
        function debounce(method, context) {     
            clearTimeout(method.tId);     
            method.tId= setTimeout(function(){         
                method.call(context);     
                }, 100);
        }
        window.onresize = function () {
            debounce(resiveDiv) //进行了函数防抖
        }
    

    3.2函数节流

    // 函数节流
    var canRun = true;
    document.getElementById("throttle").onscroll = function(){
    if(!canRun){
      return
    }
    canRun = false
    setTimeout( function () {
        console.log("函数节流")
        canRun = true
      }, 500)
    }
    

    4 自定义事件

    事件是JavaScript与浏览器交互的主要途径。
    事件是一种叫做观察者的设计模式,这是一种创建松散耦合代码的技术。

    相关文章

      网友评论

          本文标题:JavaScript 高级程序设计精要(22)

          本文链接:https://www.haomeiwen.com/subject/cekdyqtx.html