• 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

    情况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

     
    分类: JavaScript
  • 相关阅读:
    我爱Java系列之---【SpringBoot打成war包部署】
    279. Perfect Squares
    矩阵dfs--走回路
    112. Path Sum
    542. 01 Matrix
    106. Construct Binary Tree from Inorder and Postorder Traversal
    105. Construct Binary Tree from Preorder and Inorder Traversal
    Invert Binary Tree
    563 Binary Tree Tilt
    145 Binary Tree Postorder Traversal
  • 原文地址:https://www.cnblogs.com/libin-1/p/6701555.html
Copyright © 2020-2023  润新知