JavaScript知识梳理 - 闭包、原型/原型链、继承(一)

闭包、原型/原型链、继承

1.闭包

1.1.官方解释
  • 一个拥有许多变量和绑定了这些变量的环境表达式(通常是一个函数),因而这些变量也是该表达式的一部分
  • 当一个函数能够记住并访问到其所在的词法作用域及作用域链,特别强调是在其定义的作用域外进行的访问,此时该函数和其上层执行上下文共同构成闭包。
    • 闭包就是指有权访问另一个函数作用域中的变量参数的函数。
    • 闭包就是能够读取其他函数内部变量(参数)的函数
    • 闭包可以理解成定义在一个函数内部的函数
1.2.特别需要明确几点
  • 闭包一定是函数对象
  • 闭包和词法作用域、作用域链、垃圾回收机制等息息相关
  • 函数内保持对上层作用域的引用
  • 当函数在其定义的作用域外进行访问时,才产生闭包
  • 闭包是由该函数和其上层执行上下文共同构成
1.3.闭包的应用
  • 在函数外读取函数内部的变量;

  • 让局部变量的值能够被保存下来;

  • 将模块的公有属性和方法暴露出来。

  • function fn1(){
        var n = 5;
        return function fn2() {
            n++;
            return n;
        }
    }
    var fn = fn1();
    console.log( fn() );
    console.log( fn() );
    console.log( fn() );
    
1.4.变量
  • 变量无非就是全局变量和局部变量
  • Javascript语言中,函数内部可以直接读取全局变量,在函数外部无法直接读取函数内的局部变量。
1.5.作用域的概念:
  • 通常来说,一段程序代码中所用到的名字并不总是有效可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
  • 词法作用域:
    • 词法作用域,也叫静态作用域,它的作用域是指在词法分析阶段就确定了,不会改变。
    • 动态作用域,是在运行时根据程序的流程信息来动态确定的,而不是在写代码时进行静态确定的。
    • 主要区别:词法作用域是在写代码或者定义时确定的,而动态作用域是在运行时确定的。
    • 词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用。
    • javascript 使用的是词法作用域

2.原型原型链

  • JavaScript是一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言。
  • 我们把JS中的对象分为 普通对象函数对象
2.1.prototype(原型)
  • 每一个函数对象(Function.prototype除外)都有一个prototype属性(这个属性指向一个对象即原型)

  • prototype原型是函数的一个默认属性,在函数的创建过程中由JS编译器自动添加

    • 内置构造函数String、Number他们的原型指向一个普通对象,(Number{}和String{}),而Function的原型则指向函数对象function(){[native code]},这是原生代码。而这个函数对象(Function.prototype)是没有原型,即没有原型属性,所以他的prototype返回undefined。

    • var fn1 = function (){ };
      var fn2 = new Function();
      function fn3(){ };
      console.log(fn1.prototype);//{constructor:ƒ __proto__:Object}
      console.log(fn2.prototype);//{constructor:ƒ anonymous() __proto__:Object}
      console.log(fn3.prototype); // {constructor:ƒ fn3() __proto__:Object}
      //Object{} 这就是我们所说的原型,它是一个对象也叫原型对象
      // 为什么说 Function.prototype 除外呢?看代码:
      console.log(Number.prototype);//Number{constructor:ƒ Number() __proto__:Object}
      console.log(String.prototype);//String{constructor:ƒ String() __proto__:Object}
      console.log(Function.prototype);//ƒ () { [native code] }
      console.log(Function.prototype.prototype);// 结果看下图undefined
      
    • 可以看到内置构造函数Number、String等,它们的原型指向一个普通对象(Number{}和String{})

    • Function的原型则指向函数对象 function () { [native code] },这是原生代码!这个函数对象(Function.prototype)是没有原型属性的,所以它的prototype返回 undefined。

    • function Cat(){};
      Cat.prototype.name = '小白'; // 给原型对象添加属性
      Cat.prototype.color = 'black'; // 给原型对象添加属性
      Cat.prototype.sayHello = function (){ // 给原型对象添加方法
         console.log('大家好,我的名字叫'+this.name);
      }
      var cat1 = new Cat(); // 实例对象
      var obj = Cat.prototype;// 原型对象
      console.log(obj);//{name: "小白", color: "black", sayHello: ƒ, constructor: ƒ}
      console.log(cat1.constructor);//ƒ Cat(){}
      console.log(obj.constructor);//ƒ Cat(){}
      console.log(Cat.prototype === cat1.constructor.prototype);
      
    • 可以看到Cat这个构造函数,它的原型指向一个对象,即原型对象({name: “小白”, color: “black”, sayHello: ƒ, constructor: ƒ})

    • 这个原型对象的constructor(构造器)指向Cat构造函数

    • cat1实例对象的constructor(构造器)也指向Cat构造函数

2.2.constructor(构造器)
  • 每个对象都有一个隐藏属性constructor,该属性指向对象的构造函数(“类”)

  • 通过上面的代码我们可以看到,实例对象 cat1 和原型对象 obj 它们的构造器相同,都指向 Cat!我们换一种写法:

    • function Cat(){}
      Cat.prototype = {// 原型对象
         name: '小白',
         color: 'black',
         sayHello: function (){
             console.log('大家好,我的名字叫'+this.name);
         }
      }
      var cat1 = new Cat();
      console.log(Cat.prototype === cat1.constructor.prototype); //false
      console.log(Cat.prototype.constructor === Object); //true
      console.log(cat1.constructor === Object); //true
      
    • 此时 Cat.prototype 指向一个对象字面量方式定义的对象{},其构造器(constructor)指向的自然是根构造器 Object,所以 cat1 的构造器也指向根构造器 Object。

    • instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

2.3._ _ proto _ _(原型)
  • 每个对象都有一个隐藏属性_ _proto_ _即原型对象,用于指向创建他的构造函数的原型即原型对象 。

  • 也就是说: 实例._ _proto_ _===构造函数.prototype

  • 对象 通过_ _ proto _ _指向原型对象,函数对象 通过prototype指向原型对象

  • Object.prototype.jdk = 'abc123';
    Object.prototype.sayHi = function (){
       console.log('嗨~大家好');
    }
    var str = 'yoyo';
    str.sayHi(); // 嗨~大家好
    console.log(str.jdk); // 'abc123'
    
  • str 是怎么访问到 sayHi 方法和 jdk 属性的呢?

    • hasOwnProperty() 方法 ,用于判断某个属性是否为该对象自身的成员
    • 看看大致的访问过程:
      • console.log(str.hasOwnProperty('sayHi'));//false str自身没有sayHi方法console.log(str.__proto__.hasOwnProperty('sayHi'));//false 原型对象也没有sayHi方法console.log(str.__proto__.__proto__.hasOwnProperty('sayHi'));//true 原型的原型有sayHi方法str -> str._ _ proto _ _ -> str._ _ proto _ _ . _ _ proto _ _ 感觉到什么吗?
    • 我们来描述一下执行过程:
      • str.sayHi() --> 自身查找 --> 没有sayHi方法 -->
      • 查找上层原型 str._ _ proto _ _ --> 指向 String.prototype对象 --> 没有sayHi方法 -->
      • 查找上层原型 String.prototype._ _ proto _ _ --> 指向Object.prototype对象 --> 找到sayHi方法 --> 执行sayHi方法
  • var Person =function(name){
        this.name=name;
    };
    Person.prototype.getName = function(){
        console.log(this.name)
    };
    var p1 =new Person('jack');
    console.log(p1.__proto__===Person.prototype);//true
    console.log(p1.__proto__.__proto__===Object.prototype);//true
    console.log(p1.__proto__.__proto__.__proto__);null
    

    * **p1**--------**Person.prototype**--------**Object.prototype**---------null

    • 由上可知:原型链,就是在当前对象中如果自身没有该属性,则向上一层原型对象中寻找,一直到最外层(null)
    • 每个继承父函数的子函数的对象都包含一个内部属性_ _ proto _ _,该属性包含一个指针,指向父函数的prototype,若父函数的原型对象的_ _ proto _ _属性为再上一层函数的原型,在此过程中就形成了原型链。
2.4.对象关系图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZrWvEphN-1592919604588)(C:\Users\YE\Desktop\JavaScript笔记\images\Image.jpg)]

3.继承

3.1.构造函数继承
  • 原理:调用父类构造函数,并改变其中的this (bind、call、apply)

    unction Cat(n,c){ // 猫 类
       this.name = n;
       this.color = c;
       this.trait = function (){
           console.log('卖萌~');
       }
    }
    Cat.prototype.skill = function (){ // 原型上的属性方法
       console.log('抓老鼠');
    }
    // 需求:狗要卖萌,狗要多管闲事-抓老鼠
    function Dog(n,c,f){ // 狗 类
       this.food = f;
       Cat.call(this,n,c); // 狗冒充猫,访问猫的属性方法
    }
    var dog1 = new Dog('二哈','yellow','shi');// 实例对象
    console.log(dog1.name); // 二哈
    dog1.trait(); // 卖萌
    dog1.skill(); // 报错 dog1.skill is not a function
    

    我们看到这种继承方式有局限性,“父类”原型上的属性方法无法继承,所以二哈没有抓老鼠的技能

3.2.原型链继承
  • 原理:将原型对象链接到另一个对象实现继承(改变原型的指向)

    function Cat(n,c){ // 猫 类
       this.name = n;
       this.color = c;
       this.trait = function (){
           console.log('卖萌~');
       }
    }
    Cat.prototype.skill = function (){// 原型上的属性方法
       console.log('抓老鼠');
    }
    function Dog(n,c,f){ // 狗 类
       this.food = f;
    }
    Dog.prototype = new Cat(); // 把狗的原型指向猫的实例对象
    var dog1 = new Dog('二哈','yellow','shi');
    console.log(dog1.name); // undefined
    console.log(dog1.food); // shi
    dog1.trait(); // 卖萌~
    dog1.skill(); // 抓老鼠
    console.log(dog1.constructor); // Cat
    
    • 实例化对象的时候不能给“父类”传参,导致访问dog1.name没有值
    • 有句台词:‘人是人妈生的,妖是妖妈生的 ’ 现在 dog1.constructor 指向 Cat,意味着 二哈 是猫妈生的!很显然这不符合伦理,也不环保…

3.3.混合继承(组合继承)

function Cat(n,c){
   this.name = n;
   this.color = c;
   this.trait = function (){
       console.log('卖萌~');
   }
}
Cat.prototype.skill = function (){
   console.log('抓老鼠');
}
function Dog(n,c,f){
   this.food = f;
   Cat.call(this,n,c);// 对象冒充继承
}
Dog.prototype = Object.create(Cat.prototype);// 寄生继承
Dog.prototype.constructor=Dog;// 指正构造器
var dog1=new Dog('二哈','yellow','shi');
console.log(dog1.name);// 二哈
console.log(dog1.food);// shi
dog1.trait();// 卖萌~
dog1.skill();// 抓老鼠
console.log(dog1.constructor);// Dog
  • 两种方式结合可以实现相对比较完美的“继承”别忘了指正构造器(类型),不能认贼作父!

!!! 以上是DaXiong本人对前端知识的理解总结,如内容知识有错误可以留言修改。

!!!如果以上内容帮助到了你,点击一下赞或者收藏吧!

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值