Skip to content

js继承

✍️ w 🕒 2024-06-17 14:53:00(5 days ago) 🔗 A.前端知识整理
  1. 在 JavaScript 中,是通过遍历原型链的方式,来访问对象的方法和属性,简单的说就是我自身没有我就通过'proto' 找到我的构造函数的'prototype'上,构造函数原型对象没有他就去找构造函数上'proto' 链接的原型对象一直都没找到的情况下,最后找到null 终止
  2. 在原型链上查找属性比较耗时对性能有副作用,这在性能要求苛刻的情况下很重要。另外试图访问不存在的属性时会遍历整个原型链
  3. js属性遮蔽可以理解成就近原则
js
function A (name) {
    this.name = name
}
A.prototype.name = "原型对象上的name 属性"

const a = new A('w')

console.log(a.name); // w
  • 会找离自身最近属性对应值

hasOwnProperty -- 属性是否是原型对象上,证明打印是自己的而不是原型对象prototype上的,使用hasOwnProperty返回的是true 则使用的是实例对象自己的,false怎相反

js

 function Person() {}
// 每一个函数都有一个原型属性prototype
// 他们都会指向实例对象因此在Person这个
// 构造函数的prototype加属性即可创建的对象共享
Person.prototype.name = 'wang'

const p1 = new Person()
// 当前的name 到底是p1的还是 Person的
console.log(p1.hasOwnProperty('name'))

const p2 = new Person()
p2.name = 'p2'
// 当前的name 到底是p1的还是 Person的
console.log(p2.hasOwnProperty('name'))

打印结果:
false
true

继承的几种方式

原型链继承

  1. 新实例无法向父类构造函数传参
  2. 所有新实例共享父类实例的属性,导致两个实例使用同一个原型对象,如果属性是引用类型,当一个实例修改属性时,另一个实例也会受到影响。
  3. 通过直接打印对象看不到共享的属性,因为这些属性被挂载在原型链上,调用时如果实例自身没有这些属性,会访问原型链上的属性。
javascript
// 第一种 原型链继承
function Parent() {
    this.name = "parent";
    this.play = [1, 2, 3];
}

function Child() {
    this.type = "child";
}

Child.prototype = new Parent();
const child1 = new Child();
const child2 = new Child();

child1.play.push(12345);
console.log(child1.play, child2.play); // [1, 2, 3, 12345] [1, 2, 3, 12345]

构造函数继承

  1. 通过 apply() 和 call() 方法改变函数执行时 this 指向,只有当 new Child 时才执行,给当前 this 调用了一个赋值属性的封装方法。
javascript
function Parent(age) {
    this.name = "parent";
    this.age = age;
    this.play = [1, 2, 3];
}

Parent.prototype.getName = function() {
    return this.name;
};

function Child(age) {
    Parent.call(this, age);
    this.type = "child";
}

const child1 = new Child(1);
const child2 = new Child(10);

child1.play.push(12345);
console.log(child1.play, child2.play); // [1, 2, 3, 12345] [1, 2, 3]
// 报错
// console.log(child1.getName());

组合继承

  1. 结合原型链继承和构造函数继承,将二者优点结合,但会导致 Parent 执行两次,一次在创建子类原型时,一次在子类构造函数内部。
  2. 所有子类实例会拥有两份父类的属性,一份在实例自身,另一份在子类原型对象中。访问属性时优先访问实例自身的属性。

javascript
function Parent(age) {
    this.name = "parent";
    this.age = age;
    this.play = [1, 2, 3];
}

Parent.prototype.getName = function() {
    return this.name;
};

function Child(age) {
    Parent.call(this, age);
    this.type = "child";
}

// 执行一次 Parent 构造函数
Child.prototype = new Parent();
// 手动挂上构造器,指向自己的构造函数
Child.prototype.constructor = Child;

const child1 = new Child(1); // 触发函数执行第二次 Parent.call(this, age)
const child2 = new Child(10);

child1.play.push(12345);
console.log(child1.play, child2.play); // [1, 2, 3, 12345] [1, 2, 3]

原型继承

道格拉斯·克罗克福德提出的一种实现继承的方法,不使用严格意义上的构造函数,通过 Object.create 方法基于已有对象创建新对象。 这种方法并没有使用严格意义上的构造函数。它的想法是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。为了达到这个目的,他给出了如下函数。 先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例从本质上讲,object()对传入其中的对象执行了一次浅复制。 克罗克福德主张的这种原型式继承要求你必须有一个对象可以作为另一个对象的基础

这类思想就是脱离之前构造函数的想法,而是直接给对象做继承,虽然每次伪造了一个 构造函数但对其包装并未暴露,相当于都挂载到原型了导致数据共享问题,无法实现复用,属于自己的属性需要给create 第二个方法传参

javascript
function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

const parent4 = {
    name: "parent4",
    friends: ["p1", "p2", "p3"],
    getName: function() {
        return this.name;
    }
};

const person4 = Object.create(parent4);
person4.name = "tom";
person4.friends.push("jerry");

const person5 = Object.create(parent4);
person5.friends.push("lucy");

console.log(person4.name); // tom
console.log(person4.getName() === person4.name); // true
console.log(person5.name); // parent4
console.log(person4.friends); // [ 'p1', 'p2', 'p3', 'jerry', 'lucy' ]
console.log(person5.friends); // [ 'p1', 'p2', 'p3', 'jerry', 'lucy' ]

寄生式继承

寄生式继承是与原型继承相关的一种思想,通过工厂模式添加当前实例的属性和方法。

javascript
const parent5 = {
    name: "parent5",
    friends: ["p1", "p2", "p3"],
    getName: function() {
        return this.name;
    }
};

function clone(original) {
    const clone = Object.create(original);
    clone.getFriends = function() {
        return this.friends;
    };
    return clone;
}

const person5 = clone(parent5);
console.log(person5.getName()); // parent5
console.log(person5.getFriends()); // [ 'p1', 'p2', 'p3' ]

寄生组合继承

  1. 组合继承的优化方案,避免构造函数执行两次的问题。
  2. 利用 Object.create 挂载原型,减少一次父类实例创建,在子类构造函数使用 call 继承父类属性。
javascript
function clone(parent, child) {
    child.prototype = Object.create(parent.prototype);
    child.prototype.constructor = child;
}

function Parent6() {
    this.name = "parent6";
    this.play = [1, 2, 3];
}

Parent6.prototype.getName = function() {
    return this.name;
};

function Child6() {
    Parent6.call(this);
    this.friends = "child6";
}

clone(Parent6, Child6);

Child6.prototype.getFriends = function() {
    return this.friends;
};

const person6 = new Child6();
console.log(person6); // Child6 { name: 'parent6', play: [ 1, 2, 3 ], friends: 'child6' }
console.log(person6.getName()); // parent6
console.log(person6.getFriends()); // child6

Released under the MIT License.