• JavaScript高级内容笔记:原型链、继承、执行上下文、作用域链、闭包


    最近在系统的学习JS深层次内容,并稍微整理了一下,作为备忘和后期复习,这里分享给大家,希望对大家有所帮助。如有错误请留言指正,tks。

    了解这些问题,我先一步步来看,先从稍微浅显内容说起,然后引出这些概念。

    本文只用实例验证结果,并做简要说明,给大家增加些印象,因为单独一项拿出来都需要大篇幅讲解。

    1.值类型 & 引用类型

    function show(x) {
        console.log(typeof(x)); // undefined
        console.log(typeof(10)); // number
        console.log(typeof('abc')); // string
        console.log(typeof(true)); // boolean
    
        console.log(typeof(function() {})); //function
    
        console.log(typeof([1, 'a', true])); //object
        console.log(typeof({
            a: 10,
            b: 20
        })); //object
        console.log(typeof(null)); //object
        console.log(typeof(new Number(10))); //object
    }
    show();

    undefined, number, string, boolean 属于简单的值类型,不是对象;
    函数、数组、对象、null、new Number(10)都是对象;
    判断一个变量是不是对象非常简单。值类型的类型判断用typeof,引用类型的类型判断用instanceof。

    2. 语法糖(糖衣语法)

    var obj = { a: 10, b: 20 };
    var arr = [5, 'x', true];

     语法糖其实是一种“快捷方式”,上面的代码是一种缩写,完整的写法是:

    var obj = new Object();
    obj.a=10;
    obj.b=20;
     
    var arr= new Array();
    arr[0]=5;
    arr[1]='x';
    arr[2]=true;

      

    3. 对象都是通过函数创建的,而函数又是一种对象

    方法一:

    var fn = function() {
        alert(100);
    };
    fn.a = 10;
    fn.b = function() {
        alert(123);
    };
    fn.c = {
        name: "典橙贸易",
        year: 2016
    };

    fn是函数,a/b/c是fn创建的对象。

    方法二:

    var obj = { a: 10, b: 20 };
    var arr = [5, 'x', true];
     
    其本质是:
    var obj = new Object();
    obj.a = 10;
    obj.b = 20;
     
    var arr = new Array();
    arr[0] = 5;
    arr[1] = 'x';
    arr[2] = true;
     
    而
    console.log(typeof (Object)); // function
    console.log(typeof (Array)); // function
     

    4. 构造函数

    只有构造函数才有prototype属性。
    通常我们自定义的函数都属于构造函数,所以都有此属性。
    JS运行时环境内置的函数有些不是构造函数,比如alert和Math.sqrt等,就没有此属性。
    注:构造函数是指有一个内部属性[[Construct]],通过new可以创建对象的那些函数。

    5. prototype 与 __proto__

    每个函数function都有一个prototype,即原型。
    每个对象都有一个__proto__,即隐式原型。
    只有构造函数才有prototype属性。
    对象的__proto__指向的是创建它的函数的prototype。
    函数的prototype都是被Object创建的,每个自定义函数创建之初,都会有一个prototype(它是如何被创建的,不得而知!)。
    javascript_object_layout
    例如:

    function Foo(){};
    var foo = new Foo();
    Foo.__proto__ === Function.prototype // 任何函数都是由Function创建的,所以任何函数的__proto__都指向Function的prototype
    foo.__proto__ === Foo.prototype // 对象的__proto__指向的是创建它的函数的prototype
    Foo.prototype.__proto__ === Object.prototype // 如果是自定义的函数,它的prototype.__proto__ 指向 Object.prototype,因为自定义函数的prototype本质上就是和语法糖 var obj = {} 是一样的,都是被Object创建。
    Object.prototype.__proto__ === null // Object.prototype也是个对象,它的__proto__是null(这是个特例,切记切记)
     
    foo.prototype == undefined //只有函数才有prototype属性,foo是对象
    Object.prototype === (new Object()).__proto__ // 如上所述,Object本身是一个函数,(new Object())是对象
    Function.prototype.__proto__ === Object.prototype // Function是一个函数,它的prototype是被Object创建,所以它的Prototype.__proto__指向创建Object.prototype
    Function.__proto__ === Function.prototype //Function也是一个函数,函数是一种对象,也有__proto__属性。既然是函数,那么它一定是被Function创建。所以——Function是被自身创建的
    Object.__proto__ === Function.prototype //Object是个函数,也是对象,它的__proto__指向的是创建它的函数的prototype

    参考:

    http://www.cnblogs.com/wangfupeng1988/p/3978131.html
    http://www.cnblogs.com/wangfupeng1988/p/3979290.html

    6. instanceof原理

    Instanceof运算符的第一个变量是一个对象,暂时称为A;第二个变量一般是一个函数,暂时称为B。
    Instanceof的判断规则是:
      沿着A的__proto__这条线来找,如果B的prototype在这条线上,那么就返回true,否则返回false。

    如:

    function Foo(){}
    var f1 = new Foo();
    console.log(f1 instanceof Foo);    // true
    console.log(f1 instanceof Object); // true

    分析方法:
      f1的__proto__路线A:

    (1) f1.__proto__ === Foo.prototype
    (2) Foo.prototype.__proto__ === Object.prototype
    (3) Object.prototype.__proto__ === null //到终点

    结论:
      f1 instanceof Foo :A线路(1)中出现了Foo.prototype
      f1 instanceof Object :A线路(2)中出现了Object.prototype


    参考:

    http://www.cnblogs.com/wangfupeng1988/p/3979533.html

    7. 原型链

    访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。
    例如:

    function Foo(){}
    var f1 = new Foo();
    f1.a = 2;
    f1.b = 3;
    Foo.prototype.b = 6;
    Foo.prototype.c = 7;
     
    console.log(f1.a); // 2,a是f1的基本属性,直接访问没问题
    console.log(f1.c); // 7,f1的基本属性中没有c,沿着原型链向上,f1.__proto__是Foo.prototype,而Foo.prototype有c,可以访问。
    console.log(f1.b); // 3,b是f1的基本属性,直接访问没问题。可Foo.prototype还有一个b,但是不会显示,这种情况被称为“属性遮蔽”。

    判断属性是基本属性(也叫实例属性)还是原型链上的属性(也叫原型属性)使用hasOwnProperty,一般在for…in..循环中。for…in…会输出实例属性和原型属性。
    属性列表参考实例属性 vs 原型的属性 vs 静态属性
    如:

    var item;
    for(item in f1){
        console.log(item); // a,b,c
    }
     
    for(item in f1){
        f1.hasOwnProperty(item) && console.log(item); // a,b
    }

    8. 继承

    在原型链的基础上,很容易理解继承。例如在原型链的实例中,f1.hasOwnProperty就是继承自Object.prototype。
    过程如下:

    • f1基本属性中没有hasOwnProperty,沿原型链向上找:f1.__proto__是Foo.prototype;
    • Foo.prototype中也没有hasOwnProperty,继续沿原型链向上找:Foo.prototype.__proto__是Object.prototype;
    • Object.prototype中有hasOwnProperty,查找结束,允许访问hasOwnProperty。

    由于所有的对象的原型链都会找到Object.prototype,因此所有的对象都会有Object.prototype的方法。这就是所谓的“继承”。
    另外,每个函数都有的call,apply方法,和length,arguments,caller等属性也都是继承下来的。

    9. 执行上下文/执行上下文环境

    定义1:js在执行一段代码之前,会声明提前,赋值或赋初始值(即undefined)。所以同一代码段中,可以先访问,再定义,这就是“执行上下文”。
    定义2:在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空。

    分4种情况:

    第1种情况:变量、函数表达式——只提前声明,不提前赋值,默认赋值为undefined;

    console.log(a); // undefined
    var a=10;
    console.log(fn1); // undefined
    var fn1 = function(){};

    第2种情况:this关键字——提前声明并赋值

    console.log(this); // Window {external: Object, chrome: Object, document: document, __ctrip: Object, __SERVERDATE__: Object…}

    第3种情况:函数声明——提前声明并赋值

    console.log(fn2); // function fn2(){}
    function fn2(){};

    第4种情况:函数内部会创建自己独有的上下文环境

    函数每被调用一次,都会产生一个新的执行上下文环境。因为不同的调用可能就会有不同的参数。
    另外,每个函数在定义的时候(不是调用的时候),就已经确定了函数体内部自由变量的作用域。
    如:

    var a = 10;
    
    function fn() {
        console.log(a); // a是自由变量,函数创建时,就确定了a要取值的作用域
    }
    
    function bar(f) {
        var a = 20;
        f(); // 10,到创建fn的地方找变量a的作用域,而不是调用它的当前作用域。
    }
    bar(fn);

    10. 执行上下文

    如【执行上下文/执行上下文环境】所述,js代码中,会有无数的函数调用和嵌套调用,会产生无数的上下文环境,这么多上下文环境如何管理,以及如何销毁而释放内存呢?这就需要【执行上下文栈】的参与了。

    执行上下文栈:执行全局代码时,会产生一个全局上下文环境,并将该上下文环境压入栈,每次调用函数都又会产生执行上下文环境,并将该函数上下文环境也压入栈。当函数调用完成时,该函数上下文环境出栈,并消除该函数上下文环境以及其中的数据,再重新回到全局上下文环境。处于活动状态的执行上下文环境只有一个。其实这是一个压栈出栈的过程。
    执行上下文栈
    如:

    var a = 10, // 1. 进入全局上下文环境
    fn,
    bar = function(x) {
        var b = 5;
        fn(x + b); // 3. 进入fn函数上下文环境,并入栈,设为活动状态。该函数执行完毕后,bar函数上下文出栈,并及时销毁,释放内存
    };
     
    fn = function(y) {
        var c = 5;
        console.log(y + c);
    }
     
    bar(10); // 2. 进入bar函数上下文环境,并入栈,设为活动状态。该函数执行完毕后,bar函数上下文出栈,并及时销毁,释放内存
     

    参考:http://www.cnblogs.com/wangfupeng1988/p/3989357.html

    11. this

      this在执行上下文中有着非常重要的作用,这里应该说一下。

      了解this,需要记住一点:在函数中this到底取何值,是在函数真正被调用执行的时候确定的,函数定义的时候确定不了。

      大致分为以下4种情况:

    情况1:构造函数,this就代表new出来的对象。

    function Foo(){
        this.name = '典橙贸易';
        this.year = 2016;
        console.log(this); // Foo {name: "典橙贸易", year: 2016}
    }
    var f1 = new Foo(); // 所谓构造函数就是用来new对象的函数。

    另外,如果在原型链中使用this(Foo.prototype中使用this),this仍然代表new出来的对象。

    function Foo(){
        this.name = '典橙贸易2016';
    }
    Foo.prototype.getName = function(){
        console.log(this); // Foo {name: "典橙贸易2016"}
    }
    var f1 = new Foo();
    f1.getName();

    情况2:函数作为对象的一个属性,并且被对象直接调用时,this指向该对象。

    var obj = {
        name:'典橙贸易',
        fn:function(){
            console.log(this); // Object {name: "典橙贸易"}
            console.log(this.name); // 典橙贸易
        }
    }
    obj.fn(); // 直接调用
     
    // 如果不是直接调用,this就指向window
    var obj = {
        name: '典橙贸易',
        fn: function() {
            console.log(this); // Window {external: Object, chrome: Object, document: document, __ctrip: Object, __SERVERDATE__: Object…}
            console.log(this.name); // undefined
        }
    }
    var fn1 = obj.fn;
    fn1();

    情况3:函数用call或者apply调用,this就是传入的对象的值

    var obj ={
        name:'典橙贸易'
    }
    var fn = function(){
        console.log(this); // Object {name: "典橙贸易"}
        console.log(this.name); // 典橙贸易
    }
    fn.call(obj);
    // 或者
    // fn.apply(obj);

    情况4:全局 & 调用普通函数,this是window

    全局:

    console.log(this===window); // true
     
    调用普通函数:
    var name ="典橙贸易";
    var fn = function() {
        console.log(this); // Window {external: Object, chrome: Object,     document: document, __ctrip: Object, __SERVERDATE__: Object…}
        console.log(this.name); // 典橙贸易
    }
    fn();

    注意:

    var obj = {
        name: '典橙贸易',
        fn: function() {
    
            function f() {
                console.log(this); // Window {external: Object, chrome: Object,     document: document, __ctrip: Object, __SERVERDATE__: Object…}
                console.log(this.name); // undefined
            }
            f(); // 函数f虽然是在obj.fn内部定义的,但是它仍然是一个普通的函数,this仍然指向window。
    
        }
    }
    obj.fn();

    12. 作用域

    知识点1:javascript中没有块级作用域。

    var i = 2;
    if(i>1){
        var name ='典橙贸易';
    }
    console.log(name); // 典橙贸易
    for循环的{}也是类似。

    知识点2:javascript除了全局作用域之外,只有函数可以创建自己的作用域,称为函数作用域。

    如经典的问题:

    <ul id = "list">
        <li> we </li>
        <li> sdf </li>
        <li> cx </li>
        <li> h </li>
        <li> z </li>
    </ul>
    var list = document.getElementById('list');
    var e = list.getElementsByTagName('li');
    var i = 0;
    
    错误写法:
    for (; i < e.length; i++) {
      e[i].onclick = function() {
        console.log(i);
      }
    }
    
    正确写法:
    for (; i < e.length; i++) {
      (function(a) {
        e[i].onclick = function() {
          console.log(a);
        }
      })(i);
    }
    等效于:
    for (; i < 5; i++) {
      var Fn = function(a) {
        e[i].onclick = function() {
          console.log(a);
        }
      }
      Fn(i);
    }

    错误的原因:
      e[i].onclick 每次循环,只是赋值给onclick,而for循环是没有块级作用域的,所以i的值会不断累加,直到最大值5,故每次循环都会输出5。
    正确的原因:
      由于函数可以创建自己的作用域,而且各个作用域间不会相互影响,所以每次循环Fn(i)都会创建一个特有的函数作用域提供给相应的onclick,而每个作用于中的a变量也不会相互影响。

    知识点3:作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

    {
        var a = 1,
            b = 2;
    
        function fn1() {
            var a = 100,
                b = 200;
    
            function fn2() {
                var a = 1000,
                    b = 2000;
            }
        }
    } 

    三个作用域下都声明了“a和b”变量,但是他们不会有冲突。各自的作用域下,用各自的“a和b”。

    13. 自由变量及其取值规则

    在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。
    如:

    var x=1;
    function fn(){
        var b=2;
        console.log(x+b); // 这里的x就是自由变量
    }

    自由变量取值规则:要到创建 自由变量所在函数 的那个作用域中取值–是【创建】,而不是【调用】,也不是【父作用域】。

    如:

    var x = 10;
    function fn() {
        console.log(x); // fn创建了自由的函数作用域,所以无论什么地方调用它,都会输出10
    }
    function show(f) {
        var x = 20;
        (function() {
            console.log(x); // 要到x所在的匿名函数的作用域中找x,故输出20
            f(); // 要到创建fn函数的作用域中找x,故这里输出10,而不是20
        })();
    }
    show(fn);

      

    14. 作用域链

    在自由变量的基础上理解作用域链更加容易。
    在作用域Fn中使用一个变量A,如果Fn中没有定义A,则到创建Fn的那个作用域Fm中找,如果仍没有找到,则继续到创建Fm的作用域中找……依次类推,直至找到变量A或者到全局作用域中仍未找到 为止,这种跨越多个作用域的线路,就叫“作用域链”。
    代码如下:

    {
        // var A = 4; // 第4步,这是全局作用域,找到则返回4,如果到这里仍未找到,就结束,报错"A is not defined"
    
        function Fw() {
            // var A = 3; // 第3步,在作用域Fw中找,找到则返回3,否则到创建Fw的全局作用域中找
    
            function Fm() {
                // var A = 2; // 第2步,在作用域Fm中找,找到则返回2,否则到创建Fm的Fw中找
    
                function Fn() {
                    // var A = 1; // 第1步,在当前作用域找,找到则返回1,否则到创建Fn的Fm中找
                    console.log(A);
                }
                return Fn();
            }
            return Fm();
        }
        Fw();
    }

    注意:

      这里说的创建Fn的那个作用域,而不是调用Fn的那个作用域,也不是“父作用域”。详情参考【自由变量及其取值规则 > 自由变量取值规则】中的实例。

    15. 闭包

    要想理解闭包,前面的【作用域、自由变量、作用域链】三部分是基础。
    闭包的两种形式:函数作为返回值函数作为参数被传递

    第一,函数作为返回值

    function fn() {
        var max = 10;
        return function bar(x) {
            if (x > max) { // max是自由变量,取值规则,参考【自由变量及其取值规则】
                console.log(x);
            }
        }
    }
    var f1 = fn();
    f1(15); // 15

    第二,函数作为参数被传递

    var max = 10,
        fn = function(x) {
            if (x > max) { // max是自由变量,取值规则,参考【自由变量及其取值规则】
                console.log(x);
            }
        };
    (function(f) {
        var max = 100;
        f(15); // 15
    })(fn)

    另外,
    在【执行上下文栈】中说到,每个函数执行完毕,都会销毁其函数上下文环境,并清空数据。但是闭包函数执行完后,上下文环境不会被销毁。因为闭包中函数会返回或者作为参数被传递,在其他地方会被用到,一旦销毁,调用闭包的地方就无法再使用了。所以闭包会增加内容开销。

     

    参考:

      http://www.cnblogs.com/wangfupeng1988/p/3994065.html

      http://blog.csdn.net/yueguanghaidao/article/details/9568071

      http://blog.csdn.net/lmj623565791/article/details/25076713

  • 相关阅读:
    记录一下思源服务端的安装过程
    在Github上键政,然后被封号
    使用Aspnet_regiis加密web.config binzi
    策略模式
    Android Ril 分析
    20192422李俊洁 汇编语言程序设计1~4章学习笔记
    阿里云服务器如何配置开放端口
    关于DIFF插件的使用
    Python3 web开发中几种密码加密方式
    中间件安装——用Docker在CentOS7部署MongoDB服务
  • 原文地址:https://www.cnblogs.com/yinluhui0229/p/6700888.html
Copyright © 2020-2023  润新知