zoukankan      html  css  js  c++  java
  • JavaScript高级程序设计(第三版)学习笔记22、24、25章

    第22章,高级技巧

    高级函数

    安全的类型检测

    typeof会出现无法预知的行为
    instanceof在多个全局作用域中并不能正确工作
    调用Object原生的toString方法,会返回[Object NativeConstructorName]格式字符串。每个类内部都有一个[[Class]]属性,这个属性中就指定了上述字符串中的构造函数名。
    原生数组的构造函数名与全局作用域无关,因此使用toString方法能保证返回一致的值,为此可以创建如下函数:
    function isArray(value){
        return Object.prototype.toString.call(value) == "[object Array]";
    }

    也可以基于这一思路测试某个值是不是原生函数或正则表达式:

    //判断是否原生函数
    function isFunction(value){
        return Object.prototype.toString.call(value) == "[object Function]";
    }
    //判断是否原生函数
    function isFunction(value){
        return Object.prototype.toString.call(value) == "[object RegExp]";
    }
    注:对于IE中以COM对象形式实现的任何函数,isFunction都将返回false,因为他们并不是js原生函数
    注:Object的toString方法不能检测非原生构造函数的函数名。

    作用域安全的构造函数

    对于构造函数,使用了new操作符,则首先创建一个新的对象,将this指针指向新创建的对象,如果没有使用new操作符,则this指针会指向window对象,作用域安全的构造函数:
    function Person(name,age,job){
        if(this instanceof Person){          //判断this是否是正确的类型
            this.name = name;
            this.age = age;
            this.job = job;
        }else{
            return new Person(name,age,job);
        }
    }
    
    var per1 = Person("Nicholas",29,"Software Engineer");
    alert(window.name);         //""
    alert(per1.name);           //"Nicholas"
    
    var per2 = new Person("Shelby",34,"Ergonomist");
    alert(per2.name);           //"Shelby"
    建议:推荐使用作用域安全的构造函数作为最佳实践

    惰性载入函数

    对于多分支的if语句,有些时候并不需要每次都查询if语句,例如createXHR函数:
    function createXHR(){
        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");
        }
    }
    需要每次都对浏览器进行检测,根本没有必要,只要检测一次就足够了。对于此情况,解决方案:惰性载入技巧
    惰性载入表示函数分支只会执行一次。
    方法1、在函数被调用时处理函数,在第一次调用时该函数会被覆盖为另一个按合适方式执行的函数。
    function createXHR(){
        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.");
            };
        }
        return createXHR();
    }

    方法2、在声明函数时指定适当的函数,这样第一次调用函数不损失性能,在代码首次加载时会损失性能

    var createXHR = (function(){
        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){
                            //跳过
                        }
                    }
                }
                return new ActiveXObject(arguments.callee.activeXString);
            };
        }else{
            return function(){
                throw new Error("No XHR object available.");
            };
        }
    })();

    函数绑定

    函数绑定要创建一个函数,可以在特定的this环境中以指定参数调用另一个函数。该技巧常常和回调函数与事件处理程序一起使用。
    var handler = {
        message : "Event handled",
    
        handleClick : function(event){
            alert(this.message);
        }
    };
    
    var btn = document.getElementById("my-btn");
    EventUtil.addHandler(btn,"click",handler.handleClick);      //按钮按下显示undefined,不显示Event handled

    按下按钮实际显示的是undefined,并不会显示Event handled。问题在于没有保存handler.handleClick的环境,所以this最后指向了DOM按钮,而非handler(IE8中this指向window),可以使用闭包解决问题:

    var handler = {
        message : "Event handled",
    
        handleClick : function(event){
            alert(this.message);
        }
    };
    
    var btn = document.getElementById("my-btn");
    EventUtil.addHandler(btn,"click",function(event){
        handler.handleClick(event);                                             //按钮按下显示Event handled 
    });
    问题:闭包难以理解和调试。
    大部分js库为此实现了可以将函数绑定到指定环境的函数,一般叫做bind()
    简单的bind函数,接收一个函数和环境,返回一个给定函数中调用给定函数的函数,并且将所有参数原封不动传过去
    //bind函数解决方案
    function bind(fn,context){
        return function(){
            return fn.apply(context,arguments);
        };
    }

    调用方法:

    EventUtil.addHandler(btn,"click",bind(handler.handleClick,handler));
    ECMAScript5为所有函数定义了原生的bind函数,进一步简化了操作。调用方法
    EventUtil.addHandler(btn,"click",handler.handleClick.bind(handler));          //传入作为this的对象

    支持的浏览器:IE9+,Firefox4+,Chrome

    函数柯里化

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

    防篡改对象

    注意:一旦把对象定义为防篡改对象,将不可撤销,跟定义了属性的[[Configurable]]为false的结果差不多

    不可扩展对象

    使用Object.preventExtensions()方法,让你不能再给对象添加属性和方法
    var person = { name : "name"};
    Object.preventExtensions(person);
    
    person.age = 28;
    alert(person.age);          //undefined
    Object.isExtensible可检测对象是否可扩展

    密封对象

    密封对象不可扩展,而且已有成员的[[Configurable]]特性将被设置为false。意味着不能修改或删除属性和方法
    使用Object.seal()方法密封对象,使用Object.isSealed()方法确定对象是否密封。
    var person = { name : "name" };
    alert(Object.isExtensible(person));             //true
    alert(Object.isSealed(person));                 //false
    
    Object.seal(person);
    alert(Object.isExtensible(person));             //false
    alert(Object.isSealed(person));                 //true

    冻结对象

    最严格防篡改级别。冻结对象既不可扩展,又是密封,对象的数据属性的[[Writable]]特性会被设置为false。如果定义了[[Set]]函数,访问器属性仍然可写。
    使用Object.freeze()方法冻结对象,Object.isFrozen()方法检测对象是否冻结。

    高级定时器

    js是单线程的,定时器仅仅只是计划代码在未来某个时间执行,执行时间并不确定。意思就是,js是单线程的,所有需要处理的代码都要排到执行队列中去,而定时器,只是在我们设定的时间之后将代码添加到队列中去,并不一定马上执行代码。

    重复定时器

    setInterval定时器的功能缺陷:可能在代码再次被添加到队列中时,之前的代码还没有完成执行,结果导致代码运行好几次。js引擎足够聪明,能避免这个问题,在使用setInterval时,仅当没有定时器的任何其他代码实例时,才将定时器代码加入到队列。不过,还是有问题,1、某些间隔会被跳过,多个定时器的代码执行之间的间隔可能比预期小。
    使用setTimeout方法(超时调用,执行一次后再定义一个,模拟循环)来定义重复定时器可以避免这些缺点。

    函数节流

    函数节流背后的基本思想是指,某些代码不可以在没有间断的情况连续重复执行。第一次调用函数,创建一个定时器,在指定时间间隔之后运行代码。当第二次调用函数时,会清除前一次的定时器并设置另一个,如果前一个已经执行过了,此操作无意义。然而,若前一个定时器尚未执行,其实就是将其替换为一个新的定时器。目的在于只有执行函数的请求停止了一段时间后才执行。

    自定义事件

    拖放

    第24章,最佳实践

    可维护性

    特性:

    1、可理解
    2、直观
    3、可适应
    4、可扩展
    5、可调试

    代码约定

    1、可读性
    (1)使用若干空格而非制表符进行缩进
    (2)注释
    函数和方法:每个函数或方法都应该包含一个注释,用于描述目的和用于完成任务所可能使用的算法,参数意义,返回值
    大段代码:描述任务的注释
    复杂算法:解释如何做的注释
    Hack:因浏览器差异,js代码通常会包含一些hack,这些需要注释。
    2、变量和函数命名
    (1)变量名应为名词
    (2)函数名应以动词开头,getName(),返回布尔值的函数以is开头,isEnable()
    (3)使用合乎逻辑的名字,不必担心长度,长度问题可以使用后处理和压缩。
    3、变量类型透明
    很容易忘记变量所应包含的数据类型。合适的命名方式可以一定程度上缓解问题。还有以下三种表示变量数据类型的方式。
    (1)变量初始化
    (2)使用匈牙利标记法来指定变量类型。即,在变量前加一个或多个字符表示变量类型。
    (3)使用变量注释
    var found /*Boolean*/ = false;

    松散耦合

    1、解耦HTML/JavaScript
    理想情况:HTML和JavaScript应完全分离,使用外部文件和DOM附加行为来包含JavaScript
    2、解耦CSS/JavaScript
    //CSS对于JavaScript的紧密耦合
    element.style.color = "red";
    element.style.backgroundColor = "blue";
    
    //CSS对于JavaScript的松散耦合
    elements.className = "edit";
    3、解耦应用逻辑/事件处理程序

    编程实践

    1、尊重对象所有权

    意思是,不能修改不属于自己的对象。就是说,不是自己创建或维护的对象,就不要更改它的属性和方法,即
    (1)不要为实例或原型添加属性
    (2)不要为实例或原型添加方法
    (3)不要重定义已存在的方法

    2、避免全局变量

    最多创建一个全局变量

    3、避免与null进行比较

    4、使用常量

    var Constants = {
        INVALID_VALUE_MSG:"Invalid value!",
        INVALID_VALUE_URL:"/errors/invalid.php"
    };
    
    function validate(value){
        if(!value){
            alert(Constants.INVALID_VALUE_MSG);
            location.href = Constants.INVALID_VALUE_URL;
        }
    }

    性能

    注意作用域

    1、避免全局查找
    2、避免with语句

    选择正确方法

    1、避免不必要的属性查找
    2、优化循环
    (1)减值迭代在大多数情况下更高效,即循环从最大值开始
    (2)简化终止条件
    (3)简化循环体
    (4)使用后测试循环,do-while循环
    3、展开循环
    当循环次数是确定的,消除循环并使用多次函数调用往往会更快。
    Duff装置。
    4、避免双重解释

    最小化语句

    1、多变量声明

    //4个语句---浪费
    var count = 5;
    var color = "red";
    var values = [1,2,3];
    var now = new Date();
    
    //一个语句
    var count = 5;
        color = "red";
        values = [1,2,3];
        now = new Date();

    2、插入迭代值

    var name = values[i];
    i++;
    //合并
    var name = values[i++];

    3、使用数组和对象字面量

    优化DOM操作

    1、最小化现场更新(使用仓库)
    可以使用DocumentFragment,一次插入多项更改
    2、使用innerHTML
    对于大的DOM更改,innerHTML方法,比使用标准的DOM方法(createElements,appendChild之类)速度快的多。
    3、使用事件代理
    4、注意HTMLCollection

    压缩

    代码长度和配重

    代码长度:浏览器所需解析字节数
    配重:实际从服务器传送到浏览器的字节数
    1、文件压缩(使用压缩工具)
    删除额外空白
    删除所有注释
    缩短变量名
    2、HTTP压缩

    第25章,新兴API

    requestAnimationFrame()

    与动画相关

    Page VisibilityAPI

    因不知道用户是否正在与页面交互而推出的,能够让开发人员知道页面是否对用户可见

    Geolocation API

    地理定位,navigator.geolocation对象。

    File API

    宗旨:为Web开发人员提供一种安全的方式,以便在客户端访问用户计算机中的文件

    FileReader类型

    实现的是一种异步文件读取机制,方法:
    readAsText(file,encoding):以纯文本形式读取文件,保存到result属性中,第二个参数可选
    readAsDataURL(file):读取文件并将文件以数据URI的形式保存到result属性
    readAsBinaryString(file):读文件并将一个字符保存在result属性,字符串中每个字符表示一字节
    readAsArrayBuffer(file):读文件并将一个包含文件内容的ArrayBuffer保存在result属性中

    读取部分内容

    File对象支持slice方法

    对象URL

    也被称为blob URL,指的是引用在File或Blob中的数据的URL

    Web计时

    Web Workers

  • 相关阅读:
    BZOJ 1064 NOI2008 假面舞会
    p1186反素数(模板题)
    p1140【飞船控制站】
    p1164【立方体求和】
    p1103【base64编码】
    长沙集训(day不知道)伪总结(怕不是是一篇心得??)
    长沙集训day12
    长沙集训day10
    长沙集训day9
    长沙集训day8
  • 原文地址:https://www.cnblogs.com/TwinklingZ/p/5280970.html
Copyright © 2011-2022 走看看