原型、原型链、new做了什么、继承

原型和原型链

比我们通过一个构造函数new了一个新对象,构造函数的原型prototype指向一个对象,所有通过该构造函数new的新对象可以共享它所包含的属性和方法。

构造函数的原型prototype是一个对象,那么它也可以有自己的构造函数原型prototype,通过这样,形成一个原型链。原型链最终都可以上溯到Object.prototype。Object.prototype的__proto__是null。null没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null。

来看一个例子:

function Test() {}
var test = new Test();
test.__proto__===Test.prototype
Test.prototype.__proto__===Object.protype
Object.protype.__proto__===null
test.__proto__.__proto__.__proto__ === null
Test.__proto__===Function.prototype

获取对象原型API: Object.getPrototypeOf(a) 即a.proto (tip: 方法在Object上而不是Object.prototype上)

为了更清晰地理解,看以下练习:

		var obj= {}
		obj.__proto__ === Object.prototype // 为 true

		var fn = function(){}
		fn.__proto__ === Function.prototype  // 为 true
		fn.__proto__.__proto__ === Object.prototype // 为 true

		var array = []
		array.__proto__ === Array.prototype // 为 true
		array.__proto__.__proto__ === Object.prototype // 为 true
		Array.__proto__ === Function.prototype // 为 true, Array的本质为一个构造函数

实际上,看a.__proto__是什么,就看a的本质是什么:
1.new出来的对象,则指向其构造函数的prototype;
2.构造函数,则Function.prototype

new

new的过程:
初始化一个新对象
该对象的__proto__属性指向构造函数的原型prototype
将构造函数的this指向新对象,并执行函数
将新对象返回
[如果构造函数有返回一个对象(null除外),则将构造函数内的对象返回]

手写new:

function myNew(func, ...params) {
    const obj = {};
    obj.__proto__ = func.prototype
    const temp = func.apply(obj, params)
    if(temp && typeof temp === 'object') {
        return temp
    }else{
        return obj
    }
}

在这里插播一个经常面试的知识点:

手写apply, call,bind

既然知道这三个方法是用来改变this指向,那么可以利用this在方法中谁调用指向谁的特性,将函数绑定到对象上,执行后再删除

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>my call apply bind</title>
</head>
<body>
  <script>
      Function.prototype.myApply = function (context, params) {
        context.fn = this;
        context.fn(...params)
        delete context.fn
      }
      Function.prototype.myCall = function (context, ...params) {
        context.fn = this;
        context.fn(...params)
        delete context.fn
      }
      Function.prototype.myBind = function (context, params) {
        const self = this
        return function () {
            self.apply(context, params)
        }
      }
      function Person(name, age) {
        this.name = name
        this.age = age
      }
      let obj = {}
      let obj1 = {}
      let obj2 = {}
      Person.myApply(obj, ['lf', 18])
      Person.myCall(obj1, 'lf', 18)
      Person.myBind(obj2, ['lf', 18])()
  </script>
</body>
</html>

继承

既然通过原型链实例能访问到上层的一些方法和属性,那么,自然而然继承可以由他来实现。

原型链继承

最容易想到的是,让构造函数的原型指向另一个你想要继承的构造函数的实例,如下:

// 原型链继承
    // 缺点1:任何一个实例改变原型的属性,所有实例的原型属性都会变,因为指向的是同一个
    // 缺点2:没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数
    function Father(firstName) {
        this.firstName = firstName
        //属性为引用类型会被共享
        this.ver = ['1.0', '2.0']
    }
    function Son(lastName) {
        this.lastName = lastName
    }
    Son.prototype = new Father('lee')
    const a = new Son('mei')
    const b = new Son('lei')
    a.ver[0] = '3.0'
    console.log(a)
    console.log(b)

<script>
      // 借用构造函数,解决了上面的两个问题
      // 缺点1:方法都在构造函数中定义,函数无法复用
      // 缺点2:在超类型的原型中定义的方法,对子类型而言也是不可见的
      function Father(firstName) {
        this.firstName = firstName
        //属性为引用类型会被共享
        this.ver = ['1.0', '2.0']
      }
      function Son(firstName, lastName) {
        Father.call(this, firstName)
        this.lastName = lastName
      }
      const a = new Son('lee', 'mei')
      const b = new Son('han', 'lei')
      a.ver[0] = '3.0'
      console.log(a)
      console.log(b)
    </script>

    <script>
      // 组合原型链和构造函数,解决了上面的四个问题
      // 缺点1:会执行构造函数两遍,父类的属性会创建两遍
      function Father(firstName) {
        this.firstName = firstName
        //属性为引用类型会被共享
        this.ver = ['1.0', '2.0']
      }
      function Son(firstName, lastName) {
        //第二次调用
        Father.call(this, firstName)
        this.lastName = lastName
      }
      //第一次调用
      Son.prototype = new Father('tan')
      const a = new Son('lee', 'mei')
      const b = new Son('han', 'lei')
      a.ver[0] = '3.0'
      console.log(a)
      console.log(b)
    </script>

    <script>
      // 原型式继承
      // 如果目的是让一个对象与另一个对象保持类似的情况.
      // 在传入一个参数的情况下,Object.create()与 object()方法的行为相同。
      // Object.create(null)创建一个空对象,不继承object的任何方法
      function object(o) {
        function F() {}
        F.prototype = o
        return new F()
      }
      let person = { 
        name: "Nicholas", 
        friends: ["Shelby", "Court", "Van"] 
      }
      const a = object(person)
      console.log(a)
    </script>

    <script>
      // 终极版,利用原型式继承,让子构造函数原型能够调用父构造函数原型的方法
      function Father(firstName) {
        this.firstName = firstName
        this.ver = ['1.0', '2.0']
      }
      function Son(firstName, lastName) {
        Father.call(this, firstName)
        this.lastName = lastName
      }
      Father.prototype.say = function () {
        console.log(this.firstName)
      }
      // 不直接指向父类的原型,因为这样两者指向了同一个对象,实现了继承,但改变任何一个,另一个也会受影响
      Son.prototype = Object.create(Father.prototype)
      const a = new Son('lee', 'mei')
      const b = new Son('han', 'lei')
      a.ver[0] = '3.0'
      console.log(a)
      console.log(b)
    </script>

通过上面的流程也可以很好地去理解ES6中的extends。

es6 extends super

class Father {
      constructor(firstName) {
        this.firstName = firstName
        this.ver = ['1.0', '2.0']
      }
      update(firstName) {
        this.firstName = firstName
      }
    }
    class Son extends Father{
      constructor(firstName, lastName) {
        // 继承父类属性,必须有这句之后才能调用父类方法
        super(firstName)
        this.lastName = lastName
      }
      update(firstName, lastName) {
        // 调用父类方法
        super.update(firstName) 
				this.lastName = lastName
      }
    }
    // 通过观察xiao的结构可以看出,原理大概是终极版
    let xiao = new Son('lee', 'mei')

看看下面的两个例子:

function F1(){
      this.a1='aaa';
      this.b1='bbb';
      this.f1=function(){
        console.log('f1f1')
      }
    }
    F1.prototype.f2=function() {console.log('f2f2')}
    class S1 extends F1{
      k1(){
        this.f1();
      }
      k2() {
        super.f2() ;
      }
    }
      
    new S1().f1();
    new S1().k1();
    new S1().k2() ;
	function F1(){
      this.a1='aaa';
      this.b1='bbb';
      this.f1=function(){
        console.log('f1f1')
      }
    }
      
    class S1 extends F1{
      k1(){
        super.f1();
      }
    }
      
    new S1().f1();
    new S1().k1();

这些输出你做对了吗?
TIPS:当你调用super()时,解析为调用父类的constructor。当你调用super. xxx(),解析为调用父类的原型对象上的方法。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值