JS高级技巧学习小结

Posted 自由2017

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JS高级技巧学习小结相关的知识,希望对你有一定的参考价值。

安全类型检测

<script type="text/javascript">
        //判断某个值是不是原生数组
        function isArray(value){
            return Object.prototype.toString.call(value)=="[object Array]";
        }
        //判断某个值是不是原生函数
        function isFunction(value){
            return return Object.prototype.toString.call(value)=="[object Function]";
        }
        //判断某个值是不是原生正则表达式
        function isRegExp(value){
            return return Object.prototype.toString.call(value)=="[object RegExp]";
        }
    </script>

作用域安全的构造函数

构造函数其实就是一个使用new操作符调用的函数,当使用new调用时,构造函数内部用到的this对象会指向新创建的对象实例。

Person构造函数添加了一个检查并确保this对象是Person实例的if语句,它要么使用new操作符,要么在现有的Person实例环境中调用构造函数。任何一种情况,对象初始化都可以正常进行。
如果this对象不是Person的实例,那么会再次使用new操作符调用构造函数并返回结果。这样就可以确保无论是否使用new操作符,都会返回一个Person的新实例。
Demo1:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>作用域安全的构造函数</title>
    </head>
    <body>
    <script type="text/javascript">
    function Person(name,age,job){
        if(this instanceof Person)//这里检测以确保this是Person的实例
        {
           this.name=name;
            this.age=age;
            this.job=job;
        }else{
            return new Person(name,age,job);
        }

    }
    var person1=Person("liujie",23,"master");
    console.log(window.name);//""
    console.log(person1.name);//liujie
    var person2=new Person("lisi",21,"student");
    console.log(person2.name);//lisi
    </script>
    </body>
</html>

Demo2

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>作用域安全的构造函数</title>
</head>
<body>
    <script type="text/javascript">
        function Person(name, age, job){
            this.name = name;
            this.age = age;
            this.job = job;
        }
        var person1 = new Person("Nicholas", 29, "Software Engineer");
        console.log(person1);//[object Object]
        console.log(person1.name);     //"Nicholas"
        console.log(person1.age);      //29
        console.log(person1.job);      //"Software Engineer"

        var person2 = Person("Nicholas", 29, "Software Engineer");
        //这里忽略了new操作符,把构造函数作为普通函数调用
        console.log(person2);         //undefined  因为Person函数没有返回值
        console.log(window.name);     //"Nicholas"  这里this-->window
        console.log(window.age);      //29
        console.log(window.job);      //"Software Engineer"
    </script>
</body>
</html>

这里问题在于没有使用new操作符来调用该构造函数的情况上,由于该this对象是在运行时绑定的,所以直接调用Person(),this会映射到全局对象window上,导致错误对象属性的意外增加。

这里原本针对Person实例的三个属性被加到window对象上,因为构造函数是作为普通函数调用的,忽略了new操作符。这个问题是由于this对象的晚绑定造成的,在这里this被解析成了window对象。由于window的name属性是用于识别链接目标和frame的,所以这里对该属性的偶然覆盖可能会导致该页面上出现其他错误。可以创建一个作用域安全的构造函数来解决这个问题。
Demo3
在实现了作用域安全的构造函数后,如果使用构造函数窃取模式的继承且不使用原型链,那么这个继承可能被破坏。
下面的代码,Polygon构造函数是作用域安全的,然而Rectangle构造函数则不是。新创建一个Rectangle实例后,这个实例应该通过Polygon.call()来继承Polygon的sides属性。但是,由于Polygon构造函数是作用域安全的,this对象并非Polygon的实例,所以会创建并返回一个新的Polygon对象。Rectangle构造函数中的this对象并没有得到增长,同时Polygon.call()返回的值也没有用到,所以Rectangle实例中就不会有sides属性。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>作用域安全的构造函数</title>
</head>
<body>
    <script type="text/javascript">
         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.width = width;
            this.height = height;
            this.getArea = function(){
                return this.width * this.height;
            };
        }

        var rect = new Rectangle(5, 10);
        console.log(rect.sides);   //undefined
    </script>
</body>
</html>

Demo4
构造函数窃取结合使用原型链可以解决这个问题
这样一来,一个Rectangle实例也同时是一个Polygon实例,所以Polygon.call()会照原意执行,最终为Rectangle实例添加sides属性。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>作用域安全的构造函数</title>
</head>
<body>
    <script type="text/javascript">
         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.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
    </script>
</body>
</html>

惰性载入函数

惰性载入表示函数执行的分支仅会发生一次。
有两种实现惰性载入的方式,第一种就是在函数被调用时再处理函数。在第一次调用的过程中,该函数会被覆盖为另一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支了。

在这个惰性载入的createXHR()中,if语句的每一个分支都会createXHR变量赋值,有效覆盖了原有的函数。最后一步便是调用新赋的函数。下一次调用createXHR()的时候,就会直接调用被分配的函数,这样就不需要再次执行if语句了。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>惰性载入函数</title>
</head>
<body>
<script type="text/javascript">
        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];
                            } catch (ex){
                                //skip
                            }
                        }
                    }
                    return new ActiveXObject(arguments.callee.activeXString);
                };
            } else {
                createXHR = function(){
                    throw new Error("No XHR object available.");
                };
            }
            return createXHR();
        }
        var xhr1 = createXHR();
        var xhr2 = createXHR();
    </script>
</body>
</html>

第二种实现方式:在声明函数时就指定适当的函数。
这样,第一次调用函数时就不会损失性能了,而在代码首次加载时会损失一点性能。
这个例子的技巧:创建了一个匿名、自执行的函数,用以确定应该使用哪一个函数实现。每个分支都返回正确的函数定义,以便立即将其赋值给createXHR()

惰性载入函数的优点是只在执行分支代码时牺牲一点性能。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>惰性载入函数</title>
</head>
<body>
<script type="text/javascript">
        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.");
                };
            }
        })();
        var xhr1 = createXHR();
        var xhr2 = createXHR();
    </script>
</body>
</html>

函数绑定

函数绑定要创建一个函数,可以在特定的this环境中以指定参数调用另一个函数。该技巧常常和回调函数与事件处理程序一起使用,以便在将函数作为变量传递的同时保留代码执行环境。
js库实现了一个可以将函数绑定到指定环境的函数–bind()
bind()函数接收一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动传递过去。
在bind()函数中创建了一个闭包,闭包使用apply()调用传入的函数,并给apply()传递context对象和参数。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>函数绑定</title>
</head>
<body>
<input type="button" id="my-btn" value="Click Me" />
<script type="text/javascript" src="EventUtil.js"></script>
    <script type="text/javascript">
        function bind(fn, context){//接收一个函数和一个环境
            return function(){
                return fn.apply(context, arguments);
            };
        }
        var handler = {
            message: "Event handled",
            handleClick: function(event){
                console.log(this.message + ":" + event.type);//Event handled:click
            }
        };
        var btn = document.getElementById("my-btn");
        EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler));
    </script>
</body>
</html>

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

下面的例子将对象handler的handleClick方法分配为按钮的事件处理程序。当按下按钮时,就应该调用该函数,显示一个警告框。虽然貌似警告框应该显示Event handled,然而实际上显示undefined。这是因为没有保存handler.handleClick()的执行环境,所以this指向了DOM按钮而不是handler对象。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>函数绑定</title>
</head>
<body>
<input type="button" id="my-btn" value="Click Me" />
<script type="text/javascript" src="EventUtil.js"></script>
    <script type="text/javascript">
        var handler = {
            message: "Event handled",
            handleClick: function(event){
                //console.log(this);//<input id="my-btn" type="button" value="Click Me">
                console.log(this.message);//undefined
            }
        };
        var btn = document.getElementById("my-btn");
        EventUtil.addHandler(btn, "click", handler.handleClick);
    </script>
</body>
</html>

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

下面的例子将对象handler的handleClick方法分配为按钮的事件处理程序。当按下按钮时,就应该调用该函数,显示一个警告框。虽然貌似警告框应该显示Event handled,然而实际上显示undefined。这是因为没有保存handler.handleClick()的执行环境,所以this指向了DOM按钮而不是handler对象。

这里在onclick事件处理程序中使用了一个闭包直接调用handler.handleClick()。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>函数绑定</title>
</head>
<body>
<input type="button" id="my-btn" value="Click Me" />
<script type="text/javascript" src="EventUtil.js"></script>
    <script type="text/javascript">
        var handler = {
            message: "Event handled",
            handleClick: function(event){
                //console.log(this);// Object { message="Event handled",  handleClick=function()}
                console.log(this.message);//Event handled
            }
        };
        var btn = document.getElementById("my-btn");
        EventUtil.addHandler(btn, "click", function(event){
            handler.handleClick(event);
        });
    </script>
</body>
</html>

ECMAScript5为所有函数定义了一个原生的bind()方法,进一步简单了操作。
不管原生的bind方法还是自定义的bind方法,都需要传入作为this值的对象

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>bind函数绑定</title>
</head>
<body>
<input type="button" id="my-btn" value="Click Me" />
<script type="text/javascript" src="EventUtil.js"></script>
    <script type="text/javascript">
        var handler = {
            message: "Event handled",
            handleClick: function(event){
                console.log(this.message + ":" + event.type);
            }
        };
        var btn = document.getElementById("my-btn");
        EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));
    </script>
</body>
</html>

函数柯里化

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

创建方法:调用另一个函数并为它传入要柯里化的函数和必要参数
Demo1

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>函数柯里化</title>
</head>
<body>
    <script type="text/javascript">
        function curry(fn){
            //这里在arguments对象上调用了slice()方法,并传入参数1表示被返回的数组包含从第二个参数开始的所有参数
            var args = Array.prototype.slice.call(arguments, 1);//slice() 方法可从已有的数组中返回选定的元素。
            return function(){
                var innerArgs = Array.prototype.slice.call(arguments),//innerArgs表示内部函数的参数数组
                    finalArgs = args.concat(innerArgs);//将外部函数参数数组和内部函数参数数组进行连接
                    //concat()连接两个或更多的数组,并返回结果。
                return fn.apply(null, finalArgs);//这里的null表示没有考虑fn函数的执行环境
            };
        }

        function add(num1, num2){//求和函数
            return num1 + num2;
        }
        //curry()函数的第一个参数是要柯里化的函数,其他参数是要传入的值
        var curriedAdd = curry(add, 5);//这里的5是外部函数参数,3是内部函数参数
        alert(curriedAdd(3));   //8

        var curriedAdd2 = curry(add, 5, 12);//柯里化的add函数
        alert(curriedAdd2());   //17

    </script>
</body>
</html>

Demo2
函数柯里化还常常作为函数绑定的一部分包含在其中,构造出更为复杂的bind函数。
这里bind同时接受函数和一个object对象。表示给被绑定的函数的参数是从第三个开始的。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>函数柯里化</title>
</head>
<body>
    <input type="button" id="my-btn" value="Click Me">
    <script type="text/javascript" src="../EventUtil.js"></script>
    <script type="text/javascript">
        function bind(fn, context){//fn=handler.handleClick   context=handler
            var args = Array.prototype.slice.call(arguments, 2);//获取到"my-btn"
            return function(){
                var innerArgs = Array.prototype.slice.call(arguments),
                    finalArgs = args.concat(innerArgs);
                return fn.apply(context, finalArgs);//handler.handleClick.apply(handler, "my-btn");
            };
        }

        var handler = {
            message: "Event handled",
            handleClick: function(name, event){//name是要处理的元素的名字
                //event就是event对象
                console.log(this.message + ":" + name + ":" + event.type);//Event handled:my-btn:click
            }
        };

        var btn = document.getElementById("my-btn");
        EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler, "my-btn"));
    </script>
</body>
</html>

Demo3

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>函数柯里化</title>
</head>
<body>
    <input type="button" id="my-btn" value="Click Me">
    <script type="text/javascript" src="../EventUtil.js"></script>
    <script type="text/javascript">
        var handler = {
            message: "Event handled",
            handleClick: function(name, event){
                console.log(this.message + ":" + name + ":" + event.type);
            }
        };
        var btn = document.getElementById("my-btn");
        EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "my-btn"));
        //handler.handleClick.bind(handler, "my-btn")这样绑定指handler.handleClick函数中的this指向handler对象
        //bind()方法也实现了函数柯里化,只要在this的值之后再传入另一个参数即可
    </script>
</body>
</html>

防纂改对象

不可扩展对象

<script type="text/javascript">
/*
Object.preventExtensions()用来阻止给对象添加属性和方法
 */
    var person = { name: "Nicholas" };
        Object.preventExtensions(person);

        person.age = 29;
        console.log(person.age);//undefined
</script>
<script type="text/javascript">
/*
Object.preventExtensions()用来阻止给对象添加属性和方法
Object.isExtensible()方法用来判断元素是否可以扩展
 */
    var person = { name: "Nicholas" };
        console.log(Object.isExtensible(person));   //true

        Object.preventExtensions(person);
        console.log(Object.isExtensible(person));   //false

        person.age = 29;
        console.log(person.age);//undefined
</script>

密封的对象

<script type="text/javascript">
/*
Object.seal()将对象密封,不能给对象添加和删除属性和方法
 */
    var person = { name: "Nicholas" };
        Object.seal(person);

        person.age = 29;
        console.log(person.age);      //undefined

        delete person.name;
        console.log(person.name);     //"Nicholas"
</script>
<script type="text/javascript">
/*
Object.seal()将对象密封,不能给对象添加和删除属性和方法
 */
    var person = { name: "Nicholas" };
        console.log(Object.isExtensible(person));   //true  返回true表示对象可以扩展
        console.log(Object.isSealed(person));       //false   返回false表示对象没有密封

        Object.seal(person);
        console.log(Object.isExtensible(person));   //false
        console.log(Object.isSealed(person));       //true  对象被密封

        person.age = 29;
        console.log(person.age);//undefined"
</script>

冻结的对象

<script type="text/javascript">
/*
Object.freeze()方法是冻结对象,冻结的对象既不能扩展,同时也是密封的,而且对象的[[writeable]]特性也被设置为false
 */
    var person = { name: "Nicholas" };
        Object.freeze(person);

        person.age = 29;//不可以扩展
        console.log(person.age);      //undefined

        delete person.name;//不可以删除
        console.log(person.name);     //"Nicholas"

        person.name = "Greg";//因为writeable]]特性被设置为false的原因,不能被修改
        console.log(person.name);     //"Nicholas"
</script>
<script type="text/javascript">
/*
Object.freeze()方法是冻结对象,冻结的对象既不能扩展,同时也是密封的,而且对象的[[writeable]]特性也被设置为false
 */
    var person = { name: "Nicholas" };
        console.log(Object.isExtensible(person));   //true
        console.log(Object.isSealed(person));       //false
        console.log(Object.isFrozen(person));       //false  Object.isFrozen()用来判断对象是否被冻结

        Object.freeze(person);
        console.log(Object.isExtensible(person));   //false
        console.log(Object.isSealed(person));       //true
        console.log(Object.isFrozen(person));       //true

        person.age = 29;
        console.log(person.age);//undefined
</script>

高级定时器

js是运行于单线程的环境中的,定时器仅仅只是计划代码在未来的某个时间执行。执行时机是不能保证的,因为在页面的生命周期中,不同事件可能有其他代码在控制js进程。在页面下载完后的代码运行、事件处理程序、Ajax回调函数都必须使用同样的线程来执行。实际上,浏览器负责进行排序,指派某段代码在某个时间点运行的优先级。

当某个按钮被按下,它的事件处理程序代码就会被添加到队列中,并在下一个可能的时间里执行。当接收到某个Ajax响应时,回调函数的代码会被添加到队列。在js中没有任何代码时立刻执行的,但是一旦进程空闲则尽快执行。

定时器对队列的工作方式是:当特定时间过去后将代码插入。注意,给队列添加代码并不意味着对它立刻执行,而只能表示它会尽快执行。例如:设定一个150ms后执行的定时器不代表到了150ms代码就立刻执行,它表示代码会在150ms后被加入到队列中。如果在这个时间点,队列中没有其他东西,那么这段代码就会被执行,表面上看上去就好像代码就在精确的时间点上执行了。其他情况,代码可能明显等待更长时间才执行。
Demo1:
为了避免setInterval()的重复定时器的缺点,可以采用链式setTimeout()方式
调用setTimeout(),每次函数执行的时候都会创建一个新的定时器。第二个setTimeout()调用使用了arguments.callee来获取对当前执行的函数的引用,并为其设置另外一个定时器。这样做的好处:在前一个定时器代码执行完之前,不会向队列插入新的定时器代码,确保不会有任何缺失的间隔。而且,它可以保证在下一次定时器代码执行之前,至少要等待指定的间隔,避免了连续的运行。这个模式主要用于重复定时器。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>重复的定时器</title>
</head>
<body>
<div id="myDiv" style="position:absolute;width:100px;height:100px;left:0px;top:10px;background:red;"></div>
    <script type="text/javascript">
        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);
    </script>
</body>
</html>

数组分块技术

数组分块技术基本的思路:为要处理的项目创建一个队列,然后使用定时器取出下一个要处理的项目进行处理,接着再设置另一个定时器。
数组分块的重要性在于它可以将多个项目的处理在执行队列上分开,在每个项目处理之后,给予其他的浏览器处理机会运行,这样就可能避免长时间运行脚本的错误。

data.concat():当不传递任何参数调用数组的concat()方法时,将返回和原来数组中项目一样的数组。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>数组分块技术</title>
</head>
<body>
<div id="myDiv" style="background:red;"></div>
    <script type="text/javascript">
        var data = [12,123,1234,453,436,23,23,5,4123,45,346,5634,2234,345,342];
        function chunk(array, process, context){
        以上是关于JS高级技巧学习小结的主要内容,如果未能解决你的问题,请参考以下文章

React.js学习知识小结

javascript 学习小结 by FungLeo

高级正则技巧PHP正则表达式过滤html标签属性(DEMO)

转 jquery使用技巧小结

js-JavaScript高级程序设计学习笔记19

JS高级程序设计之高级技巧