第四章—中-灵析社区

懒人学前端

三、继承有哪几种方式?

JavaScript 继承方式有以下五种:

  • 原型继承
  • 借用构造函数继承
  • 组合继承
  • 寄生式继承
  • 寄生组合式继承

一、原型链

构造函数、原型和实例的关系是:每个构造函数都有一个原型对象 protptype,原型有一个属性 constructor 指向构造函数,而实例有一个内部指针 __proto__ 指向原型。如果原型是另一个类型的实例呢?原型链就是基于此构想。

function Parent () {
    this.name = "kevin";
}

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

function Child () {

}

Child.prototype = new Parent();

let child1 = new Child();

console.log(child1.getName()) // kevin

缺点:

  • 引用类型的属性被所有实例共享
  • 不能向 Parent 传参

二、借用构造函数继承

function Parent () {
    this.names = ["kevin", "daisy"];
}

function Child () {
    Parent.call(this);
}

let child1 = new Child();

child1.names.push("Lucy");

console.log(child1.names); // ["kevin", "daisy", "Lucy"]

let child2 = new Child();

console.log(child2.names); // ["kevin", "daisy"]

优点:

解决了原型链继承存在的两个缺点。

  • 避免了引用类型的属性被所有实例共享
  • 能向 Parent 传参

缺点:

方法都在构造函数中定义,每次创建实例都会创建一遍方法。

三、组合继承

function Parent (name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

Parent.prototype.getName = function () {
    console.log(this.name)
}

function Child (name, age) {
    Parent.call(this, name);
    this.age = age;
}

Child.prototype = new Parent();
Child.prototype.constructor = Child;

let child1 = new Child("kevin", "18");

child1.colors.push("black");

console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

let child2 = new Child("daisy", "20");

console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]

优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。

四、寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。

function createObj (o) {
    let clone = Object.create(o);
    clone.sayName = function () {
        console.log("hi");
    }
    return clone;
}

缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。

五、寄生组合式继承

function Parent (name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

Parent.prototype.getName = function () {
    console.log(this.name)
}

function Child (name, age) {
    Parent.call(this, name);
    this.age = age;
}

// 封装创建新的对象的方法,以传入的原型作为新对象的原型
function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

function prototype(child, parent) {
    var prototype = object(parent.prototype);
    prototype.constructor = child;
    child.prototype = prototype;
}

prototype(Child, Parent);

let child1 = new Child("kevin", "18");

child1.colors.push("black");

console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

let child2 = new Child("daisy", "20");

console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]

这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

小练习

谈谈 JavaScript 的几种继承方式各有什么优缺点。

答:见正文。

四、如何判断一个对象属于某个类?

判断对象属于某个类有四种方式:

  • typeof
  • instanceof
  • Object.prototype.construcor()
  • Object.prototype.toString()

我们具体分析一下这四种方式。

一、typeof

typeof 是用于判断数据的基本类型的,判断对象属于某个类不是特别准确。它可以判断的对象只有函数和对象。除此之外,需要注意的 typeof null 的结果是 "object",这是一个历史设计缺陷。

typeof "1"; // "string"
typeof 1; // "number"
typeof true; // "boolean"
typeof null; // "object"
typeof undefined; // "undefined"
typeof 11n; // "bigint"
typeof Symbol(); // "symbol"
typeof function() {}; // "function"
typeof {}; // "object"
typeof []; // "object"

二、instanceof

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

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
const auto = new Car('Honda', 'Accord', 1998);

console.log(auto instanceof Car); // true

console.log(auto instanceof Object); // true

我们可以利用原型链来模拟实现 instanceof

function instanceOf(objA, objB) {
	// 获取构造函数的原型对象
	objB = objB.prototype;
	// 获取对象的原型
	let proto = Object.getPrototypeOf(objA);

	// 查找,直到 null 为止
	while (proto !== null) {
		if (proto === objB) {
			return true;
		}
		proto = Object.getPrototypeOf(proto);
	}
	return false;
}

三、Object.prototype.construcor()

MDN 上对 Object.prototype.construcor() 定义:

constructor 属性返回 Object 的构造函数(用于创建实例对象)。注意,此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。

const o = {}
o.constructor === Object // true

const o = new Object
o.constructor === Object // true

const a = []
a.constructor === Array // true

const a = new Array
a.constructor === Array // true

const n = new Number(3)
n.constructor === Number // true

四、Object.prototype.toString()

toString() 方法返回一个表示该对象的字符串。可以使用它检查对象类。


const toString = Object.prototype.toString;

toString.call(new Date()); // [object Date]
toString.call(new String()); // [object String]
// Math has its Symbol.toStringTag
toString.call(Math); // [object Math]

toString.call(undefined); // [object Undefined]
toString.call(null); // [object Null]

以这种方式检查类是不可靠的,因为继承自 Object 的对象可能用它们自己的实现重写它。

const myDate = new Date();
Object.prototype.toString.call(myDate); // [object Date]

myDate[Symbol.toStringTag] = "myDate";
Object.prototype.toString.call(myDate); // [object myDate]

小练习

请模拟实现 instanceof

答案:见正文。

阅读量:923

点赞量:0

收藏量:0