美文网首页
JS高程:读书摘要(十八)高级技巧

JS高程:读书摘要(十八)高级技巧

作者: Upcccz | 来源:发表于2019-03-27 23:30 被阅读0次

一、高级函数

安全类型检测
function isArray(value){
    return Object.prototype.toString.call(value) == "[object Array]";
}
作用域安全的构造函数
function Person(name, age, job){
    if (this instanceof Person){ // new的时候 this是指向实例的
        this.name = name;
        this.age = age;
        this.job = job;
    } else { // 如果不是new来创建  直接return一个new的对象
        return new Person(name, age, job);
    }
}

如果使用上述写法,当你借用构造函数来实现继承的时候,会被破坏。Person.call(this,'a',2,'student');,此时的this的指向子类。所以需要使用组合继承来完成。

惰性载入函数
function createXHR(){
    if (typeof XMLHttpRequest != "undefined"){
        createXHR = function(){
            return new XMLHttpRequest();
        };
    } else if (typeof ActiveXObject != "undefined"){
        createXHR = function(){
            return new ActiveXObject("Microsoft.XMLHTTP");
        }
    } else {
        createXHR = function(){
            throw new Error("No XHR object available.");
         };
    }
    return createXHR();
}

第一次调用的时候就已经确定(重写)了之后所有的createXHR()函数应该的定义,下一次调用createXHR()的时候,就会直接调用被分配的函数,这样就不用再次执行if 语句了。

第二种惰性载入函数是在声明函数时就指定适当的函数。创建一个匿名、自执行的函数,用以确定应该使用哪一个函数实现。

var createXHR = (function (){
    if (typeof XMLHttpRequest != "undefined"){
        return function(){
            return new XMLHttpRequest();
        };
    } else if (typeof ActiveXObject != "undefined"){
        return function(){
            return new ActiveXObject("Microsoft.XMLHTTP");
        }
    } else {
        return function(){
            throw new Error("No XHR object available.");
         };
    }
})()
函数绑定

函数绑定要创建一个函数,可以在特定的this 环境中以指定参数调用另一个函数。该技巧常常和回调函数与事件处理程序一起使用,以便在将函数作为变量传递的同时保留代码执行环境。

function bind(fn, context){
    return function(){
        return fn.apply(context, arguments);            
    };
}

function bind(fn, context){
    var args = Array.prototype.slice.call(arguments, 2);
    return function(){
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return fn.apply(context, finalArgs);
    ;
}
函数柯里化

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

接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,返回接受处理余下的参数且返回结果的新函数。

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(){
    var args = [].slice.call(arguments)
    return args.reduce((num1,num2)=>{
        return num1+num2
    })
}

let curryAdd = curry(add,2,3)
curryAdd(1) // 6

二、防篡改对象

不可扩展对象

使用Object.preventExtensions()方法可以让你不能再给对象添加属性和方法。

var person = { name: "Nicholas" };
Object.preventExtensions(person);
person.age = 29; // 在严格模式会报错
alert(person.age); //undefined

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

密封的对象

Object.seal()方法用来密封对象,密封对象不可扩展,而且已有成员的[[Configurable]]特性将被设置为false。这就意味着不能删除属性和方法,因为不能使用Object.defineProperty()把数据属性修改为访问器属性,或者相反。属性值是可以修改的。

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

alert(Object.isExtensible(person)); //false 不可扩展
alert(Object.isSealed(person)); //true 密封对象

使用Object.isSealed()方法可以确定对象是否被密封了,被密封的对象不可扩展,因此使用Object.istExtensible()方法返回false

冻结的对象

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

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" 没有set 不能修改

Object.isFrozen()方法用于检测冻结对象。因为冻结对象既是密封的又是不可扩展的,所以用Object.isExtensible()Object.isSealed()检测冻结对象将分别返回falsetrue

三、高级定时器

除了主JavaScript执行进程外,还有一个需要在进程下一次空闲时执行的代码队列。随着页面在其生命周期中的推移,代码会按照执行顺序添加入队列。例如,当某个按钮被按下时,它的事件处理程序代码就会被添加到队列中,并在下一个可能的时间里执行。当接收到某个Ajax 响应时,回调函数的代码会被添加到队列。JavaScript中没有任何代码是立刻执行的,但一旦进程空闲则尽快执行。

设定一个150ms 后执行的定时器不代表到了150ms 代码就立刻执行,它表示代码会在150ms 后被加入到队列中。如果在这个时间点上,队列中没有其他东西,那么这段代码就会被执行,如果有,代码可能明显地等待更长时间(等待事件循环中的同步代码执行完之后)才执行。

重复定时器

使用setInterval()创建的定时器确保了定时器代码规则地插入队列中。这个方式的问题在于,定时器代码可能在代码再次被添加到队列之前还没有完成执行,结果导致定时器代码连续运行好几次,而之间没有任何停顿。幸好,JavaScript 引擎够聪明,能避免这个问题。当使用setInterval()时,仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中。

示例截图.png

可以使用链式调用setTimeout()来避免,这样做的好处是,在前一个定时器代码执行完之前,不会向队列插入新的定时器代码,确保不会有任何缺失的间隔。

setTimeout(function(){
    //处理中
    setTimeout(arguments.callee, interval);
}, interval);
Yielding Processes

运行在浏览器中的JavaScript 都被分配了一个确定数量的资源。不同于桌面应用往往能够随意控制他们要的内存大小和处理器时间,JavaScript被严格限制了,以防止恶意的Web程序员把用户的计算机搞挂了。其中一个限制是长时间运行脚本的制约,如果代码运行超过特定的时间或者特定语句数量就不让它继续执行。如果代码达到了这个限制,会弹出一个浏览器错误的对话框,告诉用户某个脚本会用过长的时间执行,询问是允许其继续执行还是停止它。

当你发现某个循环占用了大量时间,同时对于是否必须同步,是否必须按顺序完成,你的回答都是“否”,那么你就可以使用定时器分割这个循环。这是一种叫做数组分块( array chunking)的技术,小块小块地处理数组,通常每次一小块。

基本的思路是为要处理的项目创建一个队列,然后使用定时器取出下一个要处理的项目进行处理,接着再设置另一个定时器。

function chunk(array, process, context){
    setTimeout(function(){
        //取出下一个条目并处理
        var item = array.shift();
        process.call(context, item);
        //若还有条目,再设置另一个定时器
        if (array.length > 0){
          setTimeout(arguments.callee, 100);
        }
    }, 100);
}

定时器的时间间隔设置为了100ms,使得JavaScript进程有时间在处理项目的事件之间转入空闲。你可以根据你的需要更改这个间隔大小,不过100ms在大多数情况下效果不错。

函数防抖

在多次触发时,只在调用之后,在指定的时间间隔内不再次触发的情况下执行一次。实际开发多用于输入搜索,停止输入多少秒之后进行请求数据。

你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件

function fn(method,delay) {
    let timeId= null;
    return function(){
        let context = this;
        clearTimeout(timeId);
        timeId= setTimeout(function(){
            method.call(context);
        },delay);
    }   
}
// 触发了多次 都只会在最后一次调用 设置的间隔之后执行一次
函数节流

如果你持续触发事件,每隔一段时间,只执行一次事件。


function fn(method,delay) {
    var timeId= null;
    return function(){
        let context = this;
        if(!timeId){   // 定时器不存在时才执行
            timeId = setTimeout(()=>{
                method.call(context);
                clearTimeout(timeId); // 本次的定时器跑完之后 才清楚定时器让其为null
            },delay);
        }
    }   
}

function fn2(method,delay) {
    let initTime = Date.now()
    return function(){
        let context = this;
        let doTime = Date.now();
        if(doTime - initTime >= delay){
            method.call(context);
            initTime = Date.now()
        }
    }   
}

四、自定义事件(观察者模式)

观察者模式由两类对象组成:主体和观察者。主体负责发布事件,同时观察者通过订阅这些事件来观察该主体。该模式的一个关键概念是主体并不知道观察者的任何事情,也就是说它可以独自存在并正常运作即使观察者不存在。从另一方面来说,观察者知道主体并能注册事件的回调函数(事件处理程序)。涉及DOM 上时,DOM元素便是主体,你的事件处理代码便是观察者。

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

    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);
五、拖放 + 自定义函数
var DragDrop = function(){ // 自执行函数
    var dragdrop = new EventTarget(), // 观察者对象实例
          dragging = null;
          diffX = 0;
          diffY = 0;

    function handleEvent(event){
        //获取事件和目标
        var target = event.target;
        //确定事件类型
        switch(event.type){
            case "mousedown": // 鼠标按下时 获取鼠标处于目标的哪个位置 记录位置
                if (target.className.indexOf("draggable") > -1){
                    dragging = target;
                    diffX = event.clientX - target.offsetLeft;
                    diffY = event.clientY - target.offsetTop;
                    // 鼠标移动时触发 注册的dragStart事件
                    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";
                    // 鼠标移动时 触发自定义的drag事件
                    dragdrop.fire({type:"drag", target: dragging,
                                  x: event.clientX, y: event.clientY});
                }
                break;

            case "mouseup": // 鼠标放开 注销事件
                // 鼠标放开 触发自定义的dragend事件
                dragdrop.fire({type:"dragend", target: dragging,
                              x: event.clientX, y: event.clientY});
                dragging = null;
                break;
        }
    };
    //公共接口
    
    dragdrop.enable: function(){
            document.addEventListener('mousedown',handleEvent);
            document.addEventListener('mousemove',handleEvent)
            document.addEventListener('mouseup',handleEvent)
        },
    dragdrop.disable: function(){
            document.removeEventListener('mousedown',handleEvent);
            document.removeEventListener('mousemove',handleEvent)
            document.removeEventListener('mouseup',handleEvent)
        }
    }
    return dragdrop
}();
// 注册事件
DragDrop.addHandler("dragstart", function(event){
    console.log("Started dragging ")
});
DragDrop.addHandler("drag", function(event){
    console.log( "Draging ")
});
DragDrop.addHandler("dragend", function(event){
    console.log(" dragend")
});
// 此时的DragDrop拥有自定义拖动相关事件了 且能够可以被拖动的元素跟随鼠标移动

DragDrop.enable(); // 启用拖动
DragDrop.disable(); // 禁用拖动

相关文章

网友评论

      本文标题:JS高程:读书摘要(十八)高级技巧

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