Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

16、ES5 继承 #16

Open
shengq666 opened this issue Jun 28, 2020 · 11 comments
Open

16、ES5 继承 #16

shengq666 opened this issue Jun 28, 2020 · 11 comments

Comments

@shengq666
Copy link
Owner

No description provided.

@shengq666
Copy link
Owner Author

shengq666 commented Jun 28, 2020

这个也记不清楚了,晚上回去瞅瞅高程三再回头总结下。

其实 JS 是没有继承的,所谓的继承也是根据原型进行模拟继承的。实现的继承,主要就是实现以下特性:

  • 父类的属性与方法
  • 父类原型上的属性与方法

通用的一种就是 把实例属性与方法与原型上的属性与方法分开。在子类构造函数中调用父类,并将子类的原型指向父类。

function Father(value) {
    this.value = value;
}
Father.prototype.getValue = function() {
   console.log(this.value)
}

funtion Son(value) {
    Father.call(this,value)
}
Son.prototype = Object.create(Father.prototype, {
  constructor: {
    value: Son,
    enumerable: false,
    writable: true,
    configurable: true
  }
})

const child = new Son(1)

child.getValue() // 1
child instanceof Father // true

关机下班

@shengq666
Copy link
Owner Author

aaaaaaaaaaaaaaaaa,中午午休时间,办公室的阿姨们好吵啊,睡不着。

@shengq666
Copy link
Owner Author

shengq666 commented Jun 30, 2020

原型:

突然间就想到刚学 js 的时候,我很好奇为啥数组对象会有那么多方法可以调用,我明明没创建他们啊,当时太菜也没多想,又不是不能用,当时心里就觉得可能这就是因为所以科学道理人家规定的吧需要死记硬背api的。后来实习时随着api搬运的越来越熟练,我就有了更多的空闲时间,刚开始打游戏,后来游戏打烦了太无聊了就去看了高程三。然后才大概知其所以然了。_每一个JavaScript对象在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。_而这些能用的方法其实就是从原型继承而来的。

创建一个对象,展开后会看到一个 __proto__ 属性:
WeChat54b9f4c2a94c06b7781ebe1d6c2b4952

每个 js 对象都有 一个 __proto__ 属性,这个属性指向了原型,该属性是浏览器内部实现的。其实原型也是一个对象,展开 __proto__ 会发现里面有很多方法。

展开后看到有个 constructor 属性,也就是构造函数。
打开 constructor 发现里面有个 prototype属性,而且这个属性与 __proto__ 属性下的东西是一样的。也就是说:

    原型的 constructor 属性指向构造函数,而构造函数又通过 prototype 指回原型。
  • Object 是所有对象的爸爸,所有对象都可以通过 proto 找到它
  • Function 是所有函数的爸爸,所有函数都可以通过 proto 找到它
  • 函数的 prototype 是一个对象
  • 对象的 proto 属性指向原型, proto 将对象和原型连接起来组成了原型链

构造函数、实例原型与原型

function Foo() {}
var foo = new Foo()

Foo.prototype === foo.__proto__  //true
Foo.prototype.constructor === Foo  //true

//获取一个对象的原型
Object.getPrototypeOf(foo) === Person.prototype

构造函数的原型中有个constructor属性,该属性指回构造函数。
构造函数的实例中有个 __proto__属性又指回实例原型(构造函数的原型)

实例与原型

    当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。

比如基于原型链的继承中,实例中没有某个方法却可以调用,就是向上查找在原型上找到了。

原型的原型

image

Object.prototype 的原型

    console.log(Object.prototype.__proto__ === null) // true

引用阮一峰老师的 《undefined与null的区别》 就是:

null 表示“没有对象”,即该处不应该有值。

所以 Object.prototype.__proto__ 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。

所以查找属性的时候查到 Object.prototype 就可以停止查找了。

@shengq666
Copy link
Owner Author

下面是继承方面

@shengq666
Copy link
Owner Author

shengq666 commented Jun 30, 2020

1. 原型链继承

用法: 重写原型对象,通过将子类的原型指向父类的实例。

  function People() {
      this.type = 'prople';
      this.colors = ["red", "blue", "green"];
    }

    People.prototype.eat = function () {
      console.log('吃东西啦');
    }

    function Man(name) {
      this.name = name;
      this.age = 24;
    }
  Man.prototype = new People();
  var a = new Man('sq')
  var b = new Man('ax')

a 的原型 a.__proto__ 默认指向 Man.prototype ,而把 new People() 赋值给 Man.prototype 的是就相当于 把 a 的原型指向了 new People(),那么 new People() 的原型 new People().__proto__ 上的方法属性 a 都可以通过原型链进行访问到,即为父类 People.prototype 上的方法属性 a 都可以通过原型链访问到。

这个时候当我们访问 a 实例的属性或方法时会经历一下三个阶段:

  1. 搜索实例 a 的属性或者方法
  2. 搜索 Man.prototype
  3. 搜索 People.prototype

缺点:

  • 由于包含引用类型值的原型属性会被所有实例共享。原型是所有子类实例共享的,改变一个其他也会改变。
  • 没有办法在不影响所有对象实例的情况下,给父的构造函数传递参数。
a.colors.push('white')
console.log(a.colors)  // ["red", "blue", "green", "white"]
console.log(b.colors)  // ["red", "blue", "green", "white"]

@shengq666
Copy link
Owner Author

2. 借用构造函数

基本思想:在子类型构造函数的内部调用父类构造函数

为了解决原型中包含引用类型值所带来的的问题。。通过 People.call(this, name) 这样子类中继承到的每一份父类的属性方法都是新的。同时可以在子类型构造函数中向父类构造函数传递参数。

function People(name) {
    this.name = name;
     this.colors = ["red", "blue", "green"];
}
function Man (name, age) {
    //  继承了 People
    People.call(this, name)
    // 定义实例自己的属性
    this.age = age
}
var a = new Man('sq', 24)
var b = new Man('ax', 24)
a.colors.push('violet')

console.log(a.colors) 
console.log(b.colors) 

缺点:

方法都在构造函数中定 义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结 果所有类型都只能使用构造函数模式。

@shengq666
Copy link
Owner Author

shengq666 commented Jul 1, 2020

3. 组合继承

基本思想:使用原型链结合构造函数。原型链实现对原型属性与方法的继承,构造函数实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数 复用,又能够保证每个实例都有它自己的属性。这是实践中常用的继承方式。

不过这里有一点瑕疵:一个子类实例将会持有两份父类实例的数据。因为使用了原型链。

function People(name) {
    this.name = name;
     this.colors = ["red", "blue", "green"];
}
People.prototype.sayName = function() {
  return this.name
}

function Man (name, age) {
    //  继承了 People
    People.call(this, name)    // 第二次调用
    // 定义实例自己的属性
    this.age = age
}

Man.prototype = new People();    //  通过 new People()第一次调用父类拿到一份父类的实例数据
Man.prototype.constructor = Man

Man.prototype.sayAge = function() {
    return this.age
}

var a = new Man('sq', 24)
var b = new Man('ax', 24)

a.colors.push('violet')

console.log(a.colors) 
console.log(b.colors) 

console.log(a.sayName(),a.sayAge())
console.log(b.sayName(),b.sayAge())

@shengq666
Copy link
Owner Author

4.原型式继承

基本思想: 创建一个继承了父类的空子类实例,就是创建一空对象,把他挂载到另一个对象的原型链上。

function createObject(o) {
    // 子类构造函数
    function F() {}
    //  // 挂到父类实例的原型链上,实际就是原型链继承
    F.prototype = o
    return new F();
}

var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"] 
};
var anotherPerson = createObject(person); 
anotherPerson.name = "Greg"; 
anotherPerson.friends.push("Rob");

var yetAnotherPerson = createObject(person); 
yetAnotherPerson.name = "Linda"; 
yetAnotherPerson.friends.push("Barbie");
alert(person.friends);     //"Shelby,Court,Van,Rob,Barbie"

其实该函数就是Object.create()的实现

var person = {
    name: "Nicholas",
     friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg"; 
anotherPerson.friends.push("Rob");

var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda"; 
yetAnotherPerson.friends.push("Barbie");

alert(person.friends);     //"Shelby,Court,Van,Rob,Barbie"

Object.create()方法的第二个参数与 Object.defineProperties()方法的第二个参数格式相同: 每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person, { name: {
        value: "Greg"
    }
});
console.log(anotherPerson.name); //"Greg"

提醒:包含引用类型值的属性始终都会共享相应的值

@shengq666
Copy link
Owner Author

5. 寄生式继承

基本思想:在原型式继承 createObject 的基础上包裹一层,生成了子类实例后再添加一些属性方法返回

function createAnother(original){
  // 使用前面的 createObject 函数,生成了一个子类实例
  var c = createObject(original)
  // 先在子类实例上添加一点属性或方法
  c.sayHi = function(){
    console.log("hi")
  }
  // 再返回
  return c
}
    瞌睡。。。

@shengq666
Copy link
Owner Author

6. 寄生组合式继承

基本思想: 在组合继承的基础上,子类继承一个由父类原型生成的空对象。

先看下组合继承的一个例子:

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){ 
    alert(this.name);
 };
function SubType(name, age){
    SuperType.call(this, name);    // 第二次调用SuperType()
    this.age = age; 
}
SubType.prototype = new SuperType();  // 第一次调用SuperType()
SubType.prototype.constructor = SubType; 
SubType.prototype.sayAge = function(){
    alert(this.age); 
};

组合继承缺点在于继承父类函数时调用了构造函数,我们只需要优化掉这点就行了。对此可以将父类的原型赋值给了子类,并且将构造函数设置为子类,这样既解决了无用的父类属性问题,还能正确的找到子类的构造函数。

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){ 
    alert(this.name);
 };
function SubType(name, age){
    SuperType.call(this, name);  
    this.age = age; 
}

SubType.prototype = Object.create(SuperType.prototype, {
  constructor: {
    value: SubType,
    enumerable: false,
    writable: true,
    configurable: true
  }
})

var s = new SubType('sq', 24)
s.sayName()
s instanceof SuperType

@shengq666
Copy link
Owner Author

混入式继承

来自 掘金 迪斯马斯克
说白了就是把一个对象的属性复制到另一个对象上去。
比如使用 Object.assign(target, source)。这个方法将所有可枚举的属性的值从一个或多个源对象复制到目标对象,并返回目标对象。
是浅拷贝。
《继8》里的例子通过借用构造函数的方式为子类实例添加父类实例的属性,通过混入的方式为子类实例添加父类原型对象的属性:

function Mother() {
  this.a = 'mom'
}
Mother.prototype.comfort = function () {
  console.log("that's ok")
}
function Father() {
  this.b = 'dad'
}
Father.prototype.hit = function () {
  console.log("you bastard!")
}
function Me() {
  // 借用构造函数,获得了 a 和 b 两个实例属性
  Mother.call(this)
  Father.call(this)
}

// 创建一个没有实例属性的 Mother 的实例
m = Object.create(Mother.prototype)
// 修改 Me 的原型对象,现在 Me 位于 Mother 实例的原型链上了
Me.prototype = m
// 修改构造函数
Me.prototype.constructor = Me
// 再把 Father 原型对象上的属性方法复制到 Me 的原型对象 m 上
// 现在,虽然 Me 的实例并不在 Father 实例的原型链上
// 但是也可以访问 Father.prototype 上的属性方法
Object.assign(Me.prototype, Father.prototype)

me = new Me()
console.log(me)

实际上,考虑到父类的实例和父类的原型对象都是对象,所以在为子类实例添加父类实例的属性的时候,也可以直接使用混入。上面的代码可以修改为:

/**
 * Father Mother Me 的构造函数
 */
// 跳过 Object.create,直接放在 Object.assign 里
m = Object.assign({}, Mother.prototype, Father.prototype)
Me.prototype = m

me = new Me()
console.log(me)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant