美文网首页
深入理解JavaScript原型与闭包-学习笔记

深入理解JavaScript原型与闭包-学习笔记

作者: JYOKETSU3 | 来源:发表于2017-10-09 17:10 被阅读0次

    本文为深入理解javascript原型和闭包系列的摘要笔记


    1.一切都是对象

    function show(x) {
        // 值类型,不是对象
        console.log(typeof x);    // undefined
        console.log(typeof 10);   // number
        console.log(typeof 'abc'); // string
        console.log(typeof true);  // boolean
        
        // 引用类型,是对象:函数、数组、对象、null、new Number(10)都是对象 
        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();
    

    对象:若干属性的集合。

    • java或者C#中的对象都是new一个class出来的,而且里面有字段、属性、方法,规定的非常严格。
    • javascript中,数组是对象,函数是对象,对象还是对象。对象里面的一切都是属性,只有属性,没有方法。
    • javascript中,方法也是一种属性。因为它的属性表示为键值对的形式。
    var fn = function () {
        alert(100);
    };
    fn.a = 10;
    fn.b = function () {
        alert(123);
    };
    fn.c = {
        name: "王福朋",
        year: 1988
    };
    

    2.函数和对象的关系

    对象都是通过函数创建的

    函数是对象的一种

    var fn = function () { };
    console.log(fn instanceof Object);  // true
    

    对象可以通过函数来创建

    function Fn() {
        this.name = '王福朋';
        this.year = 1988;
    }
    var fn1 = new 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;
    

    3.prototype原型

    • 每个函数都有一个属性叫做prototype;
    • 这个prototype的属性值是一个对象(属性的集合)
    • 默认的只有一个叫做constructor的属性,指向这个函数本身

    如下图,左侧是一个函数,右侧的方框就是它的原型:


    SuperType()的prototype
    Object()的prototype

    可以在自己自定义的方法的prototype中新增自己的属性

    function Fn() { }
    Fn.prototype.name = '王福朋';
    Fn.prototype.getYear = function () {
        return 1988;
    };
    
    var fn = new Fn();
    console.log(fn.name);
    console.log(fn.getYear());
    
    • 即,Fn是一个函数,fn对象是从Fn函数new出来的,这样fn对象就可以调用Fn.prototype中的属性。
    • 每个对象都有一个隐藏的属性:__proto__,这个属性引用了创建这个对象的函数的prototype
    • fn.__proto__ === Fn.prototype
    • __proto__成为“隐式原型”

    4.隐式原型

    每个对象都有一个proto属性,指向创建该对象的函数的prototype

    var obj = {}
    
    • obj对象的隐式原型:obj这个对象本质上是被Object函数创建的,因此obj.__proto__=== Object.prototype,如下图:
    对象的隐式原型
    即,每个对象都有一个__proto__属性,指向创建该对象的函数的prototype
    • Object prototype的隐式原型:Object prototype也是一个对象,它的__proto__指向哪里?null,如下图:
    Object.prototype的隐式原型
    • 函数的隐式原型:函数是通过new Functoin()创建的:
    函数的隐式原型

    5.instanceof

    A instanceof B
    Instanceof的判断规则是:沿着A的proto这条线来找,同时沿着B的prototype这条线来找(B线到此为止),如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。

    function Foo() {}
    var f1 = new Foo();
    
    console.log(f1 instanceof Foo);// true
    console.log(f1 instanceof Object);// true
    
    原型链
    上图中,访问f1.b时,f1的基本属性中没有b,于是沿着proto找到了Foo.prototype.b。
    1. 对象的继承

    区分一个属性到底是基本的还是从原型中找到的:hasOwnProperty
    例:

    f1.hasOwnProperty(item)
    

    hasOwnProperty方法来自Object.prototype,如下图:

    对象的原型链
    1. 函数的继承

    • 每个函数都有callapply方法,都有lengthargumentscaller等属性
    • 函数由Function函数创建,因此继承的Function.prototype中的方法。
      函数的原型链
    • Function.prototype继承自Object.prototype的方法,因此有hasOwnProperty方法

    7.原型的灵活性

    1. 对象属性可以随时改动
    2. 如果继承的方法不合适,可以做出修改
    function Foo() {}
    var f1 = new Foo();
    
    Foo.prototype.toString = function(){
        return 'jyoketsu';
    }
    console.log(f1.toString());// jyoketsu
    
    1. 如果感觉当前缺少你要用的方法,可以自己去创建
      例如在json2.js源码中,为Date、String、Number、Boolean方法添加一个toJSON的属性:
      创建新方法

    8.简述【执行上下文】上

    • javascript在执行一个代码段之前,都会进行准备工作来生成执行上下文;
    • 代码段分为三种情况:全局代码,函数体,eval代码;
    • 准备工作中完成的数据准备称之为执行上下文或者执行上下文环境
      • 变量、函数表达式——变量声明,默认赋值为undefined
      • this——赋值
      • 函数声明——赋值

    例:

    变量:
    执行上下文-变量
    this:
    执行上下文-this
    函数声明、函数表达式:
    执行上下文-函数声明、函数表达式

    9.简述【执行上下文】下

    • 执行上下文环境:在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空
    • 函数每被调用一次,都会产生一个新的执行上下文环境
    • 函数在定义的时候(不是调用的时候),就已经确定了函数体内部自由变量的作用域
    var a = 10;
    function fn(){
        console.log(a);// a是自由变量
                       // 函数创建时,就确定了a要取值的作用域
    }
    function bar(f){
        var a = 20;
        f(); //打印"10"而不是"20"
    }
    bar(fn);
    
    function fn(x){
        console.log(arguments);// [10]
        console.log(x); // 10
    }
    fn(10);
    

    上下文环境的数据内容:

    内容类型 准备动作
    全局代码的上下文环境数据内容为:
    普通变量(包括函数表达式)如: var a = 10 声明(默认赋值为undefined
    函数声明,如: function fn() { } 赋值
    this 赋值
    如果代码段是函数体,那么在此基础上需要附加:
    参数 赋值
    arguments 赋值
    自由变量的取值作用域 赋值

    10.this

    • this指向调用该函数的对象
    • 被谁直接调用,这个this就指向谁。没有的话就是window
    • 在函数中this到底取何值,是在函数真正被调用执行的时候确定的,函数定义的时候确定不了。(因为this的取值是执行上下文环境的一部分,每次调用函数,都会产生一个新的执行上下文环境)

    在函数中this取何值

    • 情况1:函数作为构造函数
    function Foo(){
        this.name = 'jyoketsu';
        this.year = 1991;
        
        console.log(this);// Foo {name:"jyoketsu",year:1991}
    }
    
    var f1 = new Foo();
    
    console.log(f1.name);// jyoketsu
    console.log(f1.year);// 1991
    

    以上代码中,如果函数作为构造函数用,那么其中的this就代表它即将new出来的对象。
    如果直接调用Foo函数,thiswindow

    function Foo(){
        this.name = 'jyoketsu';
        this.year = 1991;
        
        console.log(this);// window {...}
    }
    
    Foo();
    
    • 情况2:函数作为对象的一个属性
      如果函数作为对象的一个属性时,并且作为对象的一个属性被调用时,函数中的this指向该对象。
    var obj = {
        x:10,
        fn:function(){
            console.log(this); // Object {x:10,fn:function}
            console.log(this.x); // 10
        }
    };
    obj.fn();
    
    var obj = {
        x:10,
        fn:function(){
            console.log(this); // Window {...}
            console.log(this.x); // undefined
        }
    };
    var fn1 = obj.fn;
    fn1();
    
    • 情况3:函数用call或者apply调用
      当一个函数被call和apply调用时,this的值就取传入的对象的值。
    var obj = {
        x:10
    }
    var fn = function(){
        console.log(this); // Object {x:10}
        console.log(this.x); // 10
    }
    fn.call(obj);
    
    • 情况4:全局 & 调用普通函数
      在全局环境下,this永远是window
    console.log(this === window); // true
    

    普通函数在调用时,其中的this也都是window:

    var x = 10;
    
    var fn = function (){
        console.log(this); // Window {...}
        console.log(this.x); // 10
    }
    fn();
    

    注意:

    var obj = {
        x:10,
        fn:function(){
            function f(){
                console.log(this); // Window {...}
                console.log(this.x); // undefined
            }
            f();
        }
    }
    obj.fn();
    

    函数f虽然是在obj.fn内部定义的,但是它仍然是一个普通的函数,this仍然指向window


    11.执行上下文栈

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

    执行上下文栈示意图
    例:
    例-执行上下文栈示意图

    12.作用域

    • javascript没有块级作用域”。所谓“块”,就是大括号“{}”中间的语句。例如if语句
    • javascript除了全局作用域之外,只有函数可以创建的作用域
    • 作用域在函数定义时就已经确定了。而不是在函数调用时确定。

    作用域是一个很抽象的概念,类似于一个“地盘”:

    作用域

    jQuery源码的最外层是一个自动执行的匿名函数:

    jQuery作用域

    13.作用域和上下文环境

    • 作用域只是一个“地盘”,一个抽象的概念,其中没有变量。
    • 要通过作用域对应的执行上下文环境来获取变量的值。
    • 作用域中变量的值是在执行过程中产生的确定的,而作用域却是在函数创建时就确定了。
    • 如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中寻找变量的值。
    作用域
    作用域与执行上下文

    14.从【自由变量】到【作用域链】

    • 自由变量:在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。
    • 自由变量的取值:
      • 1.先在当前作用域查找a,如果有则获取并结束。如果没有则继续;
      • 2.如果当前作用域是全局作用域,则证明a未定义,结束;否则继续;
      • 3.(不是全局作用域,那就是函数作用域)将创建该函数的作用域作为当前作用域;
      • 4.跳转到第一步。
    作用域链
    这个一步一步“跨”的路线,我们称之为——作用域链

    15.闭包

    闭包应用的两种情况:

    • 函数作为返回值
    • 函数作为参数传递
    1. 函数作为返回值
    function fn(){
        var max = 10;
        
        return function bar(x){
            if(x > max){
                console.log(x);
            }
        }
    }
    
    var f1 = fn();
    f1(15);
    

    如上代码,bar函数作为返回值,赋值给f1变量。执行f1(15)时,用到了fn作用域下的max变量的值。

    1. 函数作为参数被传递
    var max = 10;
    var fn = function(x){
        if(x > max){
            console.log(x);
        }
    };
    
    (function(f){
        var max = 100;
        f(15);
    })(fn);
    

    如上代码中,fn函数作为一个参数被传递进入另一个函数,赋值给f参数。执行f(15)时,max变量的取值是10,而不是100。

    • 当一个函数被调用完成之后,其执行上下文环境将被销毁,其中的变量也会被同时销毁。
    • 闭包的情况下,函数调用完成之后,其执行上下文环境不会接着被销毁。

    fn()调用完成。按理说应该销毁掉fn()的执行上下文环境,但是这里不能这么做。因为执行fn()时,返回的是一个函数。函数的特别之处在于可以创建一个独立的作用域。而正巧合的是,返回的这个函数体中,还有一个自由变量max要引用fn作用域下的fn()上下文环境中的max。因此,这个max不能被销毁,销毁了之后bar函数中的max就找不到值了。

    相关文章

      网友评论

          本文标题:深入理解JavaScript原型与闭包-学习笔记

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