JavaScript高级程序设计(第3版)学习笔记 第22章

第22章 高级技巧

1.高级函数
(1)安全的类型检测:typeof 操作符由于一些无法预知的行为,经常会导致检测数据类型时得到不靠谱的结果;instanceof 操作符在存在多个全局作用域(像一个页面包含多个 frame)的情况下,也是问题多多。

在任何值上调用 Object 原生的 toString()方法,都会返回一个[object NativeConstructorName]格式的字符串。

  • //检测是不是数组
    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]";
    }

(2)作用域安全的构造函数:避免因为忽略new 操作符在全局对象上意外设置属性。

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

(3)惰性载入函数:函数执行的分支仅会发生一次。
第一种:在函数被调用时再处理函数。

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

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

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

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

ECMAScript 5 为所有函数定义了一个原生的 bind()方法,接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动传递过去。主要用于事件处理程序以及 setTimeout() 和 setInterval(),因为性能的原因最好只在必要时使用。

  • var handler = {
        message: "Event handled",
        handleClick: function(event){
            alert(this.message + ":" + event.type);
        } 
    };
    var btn = document.getElementById("my-btn");
    EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));
    

(5)函数柯里化:用于创建已经设置好了一个或多个参数的函数。

  • //创建柯里化函数的通用方式,curry()函数的主要工作就是将被返回函数的参数进行排序,第一个参数是要进行柯里化的函数,其他参数是要传入的值。
    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);
        }; 
    }
    function add(num1, num2){
        return num1 + num2;
    }
    //创建了第一个参数绑定为 5 的 add()的柯里化版本
    var curriedAdd = curry(add, 5);
    alert(curriedAdd(3));   //8
    
    //创建了绑定了两个参数的柯里化版本
    var curriedAdd = curry(add, 5, 12);
    alert(curriedAdd());   //17

2.防篡改对象

(1) 不可扩展对象:使用 Object.preventExtensions()方法后不能再给对象添加属性和方法。

  • var person = { name: "Nicholas" };
    Object.preventExtensions(person);
    
    person.age = 29;
    alert(person.age); //undefined

         使用 Object.istExtensible()方法还可以确定对象是否可以扩展。

  • var person = { name: "Nicholas" };
    alert(Object.isExtensible(person)); //true
    
    Object.preventExtensions(person);
    alert(Object.isExtensible(person)); //false

(2) 密封的对象:不可扩展,不能删除属性和方法

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

         使用 Object.isSealed()方法可以确定对象是否被密封了

  • var person = { name: "Nicholas" };
    alert(Object.isExtensible(person)); //true
    alert(Object.isSealed(person));     //false
    
    Object.seal(person);
    alert(Object.isExtensible(person)); //false
    alert(Object.isSealed(person));     //true
    

(3) 冻结的对象:最严格的防篡改级别,既不可扩展,又是密封的,而且对象数据属性的[[Writable]]特性会被设置为 false。

  • var person = { name: "Nicholas" };
    Object.freeze(person);
    person.age = 29; 
    alert(person.age); //undefined
    
    delete person.name; 
    alert(person.name); //"Nicholas"
    
    person.name = "Greg"; 
    alert(person.name); //"Nicholas"

         Object.isFrozen()方法用于检测冻结对象。

  • var person = { name: "Nicholas" };
    alert(Object.isExtensible(person));//true
    alert(Object.isSealed(person));//false
    alert(Object.isFrozen(person));//false
    
    Object.freeze(person);
    alert(Object.isExtensible(person));//false
    alert(Object.isSealed(person));//true
    alert(Object.isFrozen(person));//true

3.高级定时器:JavaScript 是运行于单线程的环境中的,定时器仅仅只是计划代码在未来的某个时间执行,执行时机是不能保证的。浏览器负责进行排序,指派某段代码在某个时间点运行的优先级。

定时器对队列的工作方式:当特定时间过去后将代码插入,并不意味着对它立刻执行,而只能表示它会尽快执行。设定一个 150ms 后执行的定时器不代表到了 150ms 代码就立刻执行,它表示代码会在 150ms 后被加入到队列中。

关于定时器要记住的最重要的事情是,指定的时间间隔表示何时将定时器的代码添加到队列,而不是何时实际执行代码。

(1)重复的定时器:使用setInterval()创建的定时器确保了定时器代码规则地插入队列中,但是定时器代码可能在代码再次被添加到队列之前还没有完成执行,会导致定时器代码之后连续运行好几次。虽然JavaScript 引擎能避免这个问题,但是会导致新的两个问题:

  • 某些间隔会被跳过。
  • 多个定时器的代码执行之间的间隔可能会比预期的小。

可以使用链式 setTimeout() 调用避免这两个缺点。

  • /*在前一个定时器代码执行完之前,不会向队列插入新的定时器代码,确保不会有任何缺失的间隔。
    保证在下一次定时器代码执行之前,至少要等待指定的间隔,避免了连续的运行。*/
    setTimeout(function(){
        var div = document.getElementById("myDiv");
        left = parseInt(div.style.left) + 5;
        div.style.left = left + "px";
        if (left < 200){
            setTimeout(arguments.callee, 50);
        } 
    }, 50);

(2)Yielding Processes:脚本长时间运行的问题通常是由两个原因之一造成的:过长的、过深嵌套的函数调用或者是进行大量处理的循环。在展开该循环之前,你需要回答以下两个重要的问题:

  • 该处理是否必须同步完成?
  • 数据是否必须按顺序完成?
  • 当某个循环占用了大量时间,同时对于上述两个问题,回答都是“否”,可以使用数组分块(array chunking)的技术小块小块地处理数组。

在数组分块模式中,array 变量本质上就是一个“待办事宜”列表,包含了要处理的项目。使用 shift()方法可以获取队列中下一个要处理的项目,然后将其传递给某个函数。如果在队列中还有其他项目,则设置另一个定时器,并通过 arguments.callee 调用同一个匿名函数。

 

  • //接受三个参数:要处理的项目的数组,用于处理项目的函数,以及可选的运行该函数的环境。
    function chunk(array, process, context){
        setTimeout(function(){
            var item = array.shift();
            process.call(context, item);
            if (array.length > 0){
                setTimeout(arguments.callee, 100);
            }
        }, 100);
    }
    
    var data = [12,123,1234,453,436,23,23,5,4123,45,346,5634,2234,345,342];
    function printValue(item){
        var div = document.getElementById("myDiv");
        div.innerHTML += item + "<br>";
    }
    chunk(data, printValue);
    //使用concat()方法保证原数组不变
    chunk(data.concat(), printValue);

(3)函数节流:某些代码不可以在没有间断的情况连续重复执行。只要代码是周期性执行的,都应该使用节流。

  • //接受两个参数:要执行的函数以及在哪个作用域中执行。
    function throttle(method, context) {
        clearTimeout(method.tId);
        method.tId= setTimeout(function(){
            method.call(context);
        }, 100);
    }

节流在 resize 事件中是最常用的。

  • function resizeDiv(){ 
        var div = document.getElementById("myDiv");
        div.style.height = div.offsetWidth + "px";
    }
    window.onresize = function(){
        throttle(resizeDiv);
    };

 

4.自定义事件:创建一个管理事件的对象,让其他对象监听那些事件。

function EventTarget(){
    this.handlers = {};
}
EventTarget.prototype = {
    constructor: EventTarget,
    //注册给定类型事件的事件处理程序,接受两个参数:事件类型和用于处理该事件的函数。
    addHandler: function(type, handler){
        if (typeof this.handlers[type] == "undefined"){
            this.handlers[type] = [];
        }
        this.handlers[type].push(handler);
    },
    //触发一个事件,接受一个参数,一个至少包含 type 属性的对象。
    fire: function(event){
        if (!event.target){
            event.target = this;
        }
        if (this.handlers[event.type] instanceof Array){
            var handlers = this.handlers[event.type];
            for (var i=0, len=handlers.length; i < len; i++){
                handlers[i](event);
            }
        } 
    },
    //注销某个事件类型的事件处理程序,接受两个参数:事件的类型和事件处理程序。
    removeHandler: function(type, handler){
        if (this.handlers[type] instanceof Array){
            var handlers = this.handlers[type];
            for (var i=0, len=handlers.length; i < len; i++){
                if (handlers[i] === handler){
                    break;
                } 
            }
            handlers.splice(i, 1);
        }
    }
};

function handleMessage(event){
    alert("Message received: " + event.message)
}
//创建一个新对象
var target = new EventTarget();

//添加一个事件处理程序 
target.addHandler("message", handleMessage);

//触发事件
target.fire({ type: "message", message: "Hello world!"});

//删除事件处理程序 
target.removeHandler("message", handleMessage);
//再次,应没有处理程序
target.fire({ type: "message", message: "Hello world!"});

5.拖放对于桌面和 Web 应用都是一个非常流行的用户界面范例,它能够让用户非常方便地以一种直 观的方式重新排列或者配置东西。

var DragDrop = function(){
    var dragdrop = new EventTarget(),
        dragging = null,
        diffX = 0,
        diffY = 0;

    function handleEvent(event){
        //获取事件和对象
        event = EventUtil.getEvent(event);
        var target = EventUtil.getTarget(event);

        //确定事件类型
        switch(event.type){
            case "mousedown":
                if (target.className.indexOf("draggable") > -1){
                    dragging = target;
                    diffX = event.clientX - target.offsetLeft;
                    diffY = event.clientY - target.offsetTop;
                    dragdrop.fire({type:"dragstart", target: dragging,
                           x: event.clientX, y: event.clientY});
                }
            break;
            case "mousemove":
                if (dragging !== null){
                    //指定位置
                    dragging.style.left = (event.clientX - diffX) + "px";   
                    dragging.style.top = (event.clientY - diffY) + "px";

                    //触发自定义事件
                    dragdrop.fire({type:"drag", target: dragging,x: event.clientX, y: event.clientY});
                }
            break;
            case "mouseup":
                dragdrop.fire({type:"dragend", target: dragging,x: event.clientX, y: event.clientY});
                dragging = null;
            break; 
        }
    };
    //公共接口
    dragdrop.enable = function(){
        EventUtil.addHandler(document, "mousedown", handleEvent);
        EventUtil.addHandler(document, "mousemove", handleEvent);
        EventUtil.addHandler(document, "mouseup", handleEvent);
    };
    dragdrop.disable = function(){
        EventUtil.removeHandler(document, "mousedown", handleEvent);                 
        EventUtil.removeHandler(document, "mousemove", handleEvent);         
        EventUtil.removeHandler(document, "mouseup", handleEvent);
    };
    return dragdrop;
}();

DragDrop.addHandler("dragstart", function(event){
    var status = document.getElementById("status");
    status.innerHTML = "Started dragging " + event.target.id;
});
DragDrop.addHandler("drag", function(event){
    var status = document.getElementById("status");
    status.innerHTML += "<br/> Dragged " + event.target.id + " to (" + event.x +
                         "," + event.y + ")";
});
DragDrop.addHandler("dragend", function(event){
    var status = document.getElementById("status");
    status.innerHTML += "<br/> Dropped " + event.target.id + " at (" + event.x +"," + event.y + ")";
});

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值