美文网首页
ES5的核心技术

ES5的核心技术

作者: lj51 | 来源:发表于2018-03-19 18:12 被阅读0次

    标签 : ES5


    1.闭包

    观察下一段代码在浏览器中的运行情况

        (function(){
            var a = 30;
        })();
        alert(a);   //  Uncaught ReferenceError: a is not defined
    

    浏览器会报错,因为上面代码段1-3行形成了闭包,从而导致闭包外面无法访问到闭包内的变量。

    我们再观察这么一段代码:

        (function(){
            alert(a);   //  undefined
            var a = 30;
        })();
    

    上面这段代码结果是aundefined,和上面不同,undefined是指a声明了,但是没有赋值,而上面是报错是因为作用域中没有a这个变量。

    接着看下面这段代码

        function f1() {
            var a = 20;
            function f2() {
                a += 1;
                console.log(a);
            }
            return f2;
        }
    
        var r = f1();
        r();    //  21
        r();    //  22
        r();    //  23
    

    我们在函数作用域外面访问到了f1中的变量,这就是闭包的作用,让我们可以拿到本不该拿到的东西。

    内存泄漏

    我们可能经常听说闭包会导致内存泄漏,那么为什么闭包会导致内存泄漏呢?

    一般函数在执行完的时候,js的垃圾回收机制就会回收函数内的变量,释放内存。但是,上面我们把f2函数给 return 出来了,里面使用到了a变量,而浏览器不知道我们执行了上面三次之后什么时候再使用个函数,所以a变量就一直在内存中存在了,从而可能会导致内存泄漏。那么,我们怎么防止发生内存泄露呢?一般情况下,我们在不使用f2的时候,把f2置为 null。

        f2 = null;
    

    有些时候,我们想保护我们的变量,这时候可以用到闭包,不让外部直接访问和修改我们的变量。我们可以通过下面的方式实现:

        function Man() {
            var name;
            this.setName = function (value) {
                name = value;
            }
        }
        var s = new Man();
        s.setName('lemon');
    

    现在,name 变量就被我们保护了,切记不用的时候一定要释放内存。

    现在我们看一个我们经常看到的一个例子:

        var list_li = document.getElementsByTagName('li');  //  假设有6个li
        for(var i = 0;i<list_li.length;i++) {
            list_li[i].onclick = function () {
                console.log(i);
            }
        }
    

    我们的目的是去给每一个li绑定一个事件,点击第 i 个我们就输出对应 i 的值,但是我们发现结果都是6。怎么去修改处我们想要的结果,也许都知道。

        var list_li = document.getElementsByTagName('li');
        for(var i = 0;i<list_li.length;i++) {
            (function (i) {
                list_li[i].onclick = function () {
                    console.log(i);
                }
            })(i);
        }
    

    为什么上面的代码执行结果和这次的不一样呢?

    在js分为 同步队列异步队列,只有同步队列里面的代码执行完了才会去异步队列里面拉取执行。

    异步队列只包含以下三个:

    • 事件绑定
    • Ajax
    • setTimeout

    接下来,我们就清楚了,事件绑定是异步队列,而for循环是同步队列,当for结束的时候,i的值自然就是最后一个值。但是,我们加上闭包以后,将i传进去,但是函数没有执行完,因为事件绑定在异步队列中,此时i就会保存在内存中,一直到对应的事件绑定事件结束,i才会被垃圾回收机制给回收掉。

    上面这一整句话都是因为js在es5中只有函数级作用域,没有块级作用域,而es6中引入的let则会产生块级作用域,这里不做赘述。


    2. 模块化

    立即执行函数实现模块化

        var module = (function () {
            var a =5;
            function add(x) {
                var b = a + x;
                console.log(b);
            }
    
            return {
                des:'这是一个模块',
                add:add
            }
        })();
    
        module.add(3)
    

    加入你感觉这种写法过于函数时,你也可以写成这样:

        var module = {
            a:5,
            add:function (x) {
                console.log(this.a + x)
            }
        };
    
        module.add(30)
    

    3. this

        var a = 20;
        var p = {
            a:30,
            test:function () {
                alert(this.a)
            }
        }
        
        p.test();    //  30
        var q = p.test();
        q();    //  20
    

    p.test() 中 this 指向 p ,所以 a 的值为30。上面的 var q = p.test() 相当于下面的代码:

        var q = function () {
                alert(this.a)
            }
    

    此时 this 指向的是全局,所以 a 的值为20。

    观察下面的代码

        var a = 20;
        var p = {
            a:30,
            test:function () {
            
                function s() {
                    alert(this.a)
                }
                s();
                
            }
        };
        p.test()    //20
    

    在函数里面执行 s ,我们去找 s 的宿主,假如我们没有找到s的宿主,默认就会去找到window。


    4. ES5中的类

    在别的语言中,类的构造函数需要我们显式的声明:

        class c{
            constructor{
                this.a = 20;
            }
        }
    

    但是在js中,构造函数和初始化这个类就是一个东西

        var People = function (sex) {
            this.sex = sex;  // 构造函数和初始化这个类就是一个东西
        };
        People.prototype.hobby = function () {
            console.log(this.sex + '喜欢女人!!')
        };
    

    父类声明好了,继续写我们的子类

    第一种写法

        var Lemon = function () {
            
        };
        Lemon.prototype = People.prototype;
    

    乍一看没啥问题啊,子类的原型等于父类的原型。但是,我们继续做一件事情:

        Lemon.prototype.test = function () {
            
        }
    

    我们给子类的原型上挂载一个方法,此时我们去consol.log 父类,发现父类的原型上也多了一个test方法。子类只是继承我们的父类,怎么可以修改呢!!!至于为什么呢?因为原型的传递是按引用传递。
    所以,第一种方法是不可行的。

    第二种写法

    子类不是要把父类的方法和属性都继承过来么,那就这样写:

        var Lemon = function (sex) {
            People.call(this,sex);  //  把People的属性继承过来
        };
        Lemon.prototype = new People();  //  把People的方法继承过来
        var l = new Lemon('Man');
        console.log(l)
    

    这样写看起来还不错,方法和属性都继承过来了。但是,我们修改我们的父类如下:

        var People = function (sex) {
            this.sex = sex;
            console.log('666')
        };
    

    现在我们new Lemon的时候,会发现输出了 两次 666,也就是构造函数执行了次,而一旦我们构造函数里面的东西比较多,这时候就变得比较坑了。这么看来,这第二种方法也不怎么好,故舍弃。

    通过上面两种方法,我们仔细观察,子类的构造函数的执行都是指向了父类。所以,我们需要了解一下我们现在的需求:

    • 拿到父类原型链上的方法
    • 不能让构造函数执行两次
    • 引用的原型链不能是按址引用
    • 修正子类的constructor

    基于以上的需求:

        var Lemon = function (sex) {
            People.call(this,sex);  //  把People的属性继承过来
        };
        var __bak = Object.create(People.prototype);    //  创建一个副本
        __bak.constructor = Lemon;  //  修正构造函数的指向
        Lemon.prototype = __bak;  //  把People的方法继承过来
        var l = new Lemon('Man');
        console.log(l)
    

    上面就是所谓的我们基于 js 实现面向对象的一个过程。


    5. bind

        var user = {
            age:20,
            init:function () {
                console.log(this.age)
            }
        };
    
        var data = {
            age: 40
        };
        var s = user.init.bind(data);
        s();
    

    这里我们要注意,bind之后返回的是一个新的对象。


    总结

    1.立即执行函数

    2.闭包

    • 内部函数可以访问外部函数的变量,把内部函数返回出去就可以保护内部的变量。
    • 闭包容易造成内存泄漏,避免的方法就是 不用的时候将 return出去的变量置为 null。

    3.原型链

    • 构造函数的属性比原型链上的属性的优先级要高;
    • 面向对象编程的时候,js没有类的概念,可以用函数来替代;
    • constructor 实际就是对应那个函数;
    • prototype 是按引用传递的,可以用 Object.create 创建原型链的副本

    4. 数据传递类型

    • 数值 字符串 布尔类型 都是按传递;
    • 对象数组都是按引用传递;

    5.改变this的指向,其中bind返回一个新对象

    • call
    • apply
    • bind

    6.函数提升的优先级比变量优先级要

    7.jq 内部有很多经典的写法 模块化编程的概念 闭包等等

    相关文章

      网友评论

          本文标题:ES5的核心技术

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