原型、原型链、及拓展(ES5的继承模式)
2021-06-22 本文已影响0人
青城墨阕
原型
- 概念:
- 原型就是一个对象
- 每个函数都有一个prototype的属性,该属性指向的是当前函数的显示原型对象
- 每个实例对象都有一个____proto____属性,该属性指向当前实例对象的隐式原型对象
- 构造函数的显示原型对象 === 当前构造函数的实例对象的隐式原型对象
let Person = function() {};
// 1. 每个函数都偶有一个属性prototype
console.log(Person.prototype); // 空对象 ------> 原型对象(隐式原型对象)
let person1 = new Person(); // 生成实例对象
// 2. 每个实例对象身上都有一个属性__proto__,该属性指向当前实例对象的原型对象(隐形原型对象)
// 3. 构造函数的显示原型对象 === 当前构造函数实例对象的隐式原型对象
Person.prototype === person1.__proto__; // true
- 对象:
- 实例对象(只可用「对象.属性」调用)
- 函数对象(可用「对象.属性」调用,也可用「对象()」的方式调用)
原型链
- 当使用 「对象.属性」 的时候,先从自身去查找有无该属性。如果没有,会沿着____proto____原型链去找,直到找到Object(Object是一个函数对象)的原型对象,如果还没有则返回undefined。
- ____proto____这条链就是原型链。
a.b
// 找a会沿着 作用域 找,找b(对象的属性)会沿着原型链找
原型链图解.png
原型拓展
拓展一:instanceof
运算符
instanceof
是如何判断的?
- 表达式:A
instanceof
B - 如何B函数的显示原型对象在A对象的原型链上,返回
true
,否则返回false
let Person = function() {}; // 构造函数
let person1 = new Person(); // 创建实例对象
person1 instanceof Person; // true (person1.__proto__ === Person.prototype)
person1 instanceof Object; // true (Person.__proto__.__proto__ === Object.prototype)
person1 instanceof Function; // false
Person instanceof Function; // true
手动封装一个 instanceof
- Object.getPrototypeOf() 方法返回指定对象的原型(内部[[Prototype]]属性的值)。
function myInstanceof(left, right) {
// 获取对象的原型
let proto = Object.getPrototypeOf(left)
// 获取构造函数的 prototype 对象
let prototype = right.prototype;
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
// 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
proto = Object.getPrototypeOf(proto);
}
}
拓展二:Object.getPropertyNames()
、Object.keys()
与for..in
区别
由上图可见,for..in
可以遍历对象上所有的属性,包括原型属性;而
Object.getPropertyNames()
与 Object.keys()
只能遍历对象中可枚举的属性。
另,obj.hasOwnProperty(propName)
方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键),不包括原型链上的属性。故,一般与for..in
配合使用。
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 处理自身属性
}
}
拓展三:ES5的继承模式
1. 原型链继承
// 父类
function Animal(name, age) {
this.name = name;
this.age = age;
this.attrObj = {
type: 'animal'
};
}
Animal.prototype.eat = function() {
console.log(this.name + '在吃东西');
}
// 子类
function Cat(name, age) {
this.name = name;
this.age = age;
}
Cat.prototype = new Animal(); // 让子类的原型 成为 父类的实例对象
let tom = new Cat('tom', 3);
tom.eat(); // tom在吃东西
tom的原型链
-
缺点
i) 无法向父类的构造方法传参
ii) 所有Cat的实例对象的原型链都会指向同一个Animal实例, 因此对某个Cat实例的来自父类的引用类型变量修改会影响所有的Cat实例
let momo = new Cat('momo', 3);
momo.attrObj.type = '波斯猫';
let kate = new Cat('kate', 2);
kate.attrObj.type // 波斯猫
2. 构造函数继承
// 父类
function Animal(name, age) {
this.name = name;
this.age = age;
}
Animal.prototype.eat = function() {
console.log(this.name + '在吃东西');
}
function Cat(name, age, sex) {
this.sex = sex;
Animal.call(this, name, age); // 利用call改变this指向
}
let tom = new Cat('tom', 3, 'boy');
tom当前原型链
-
缺点
继承不到父类原型上的属性和方法
3. 组合继承
// 父类
function Animal(name, age) {
this.name = name;
this.age = age;
}
Animal.prototype.eat = function() {
console.log(this.name + '在吃东西');
}
// 子类
function Cat(name, age, sex) {
this.sex = sex;
Animal.call(this, name, age); // 利用call改变this指向
}
Cat.prototype = new Animal(); // 让子类的原型 成为 父类的实例对象
let tom = new Cat('tom', 3, 'boy');
tom.eat(); // tom在吃东西
tom当前的原型链
-
缺点
每次创建子类实例都执行了两次构造函数(Animal.call和 new Animal()),虽然这并不影响对父类的继承,但子类创建实例时,原型中会存在两份相同的属性和方法,这并不优雅。
4. 寄生式组合继承
解决构造函数被执行两次的问题, 我们将指向父类实例改为指向父类原型, 减去一次构造函数的执行。
- Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)
// 父类
function Animal(name, age) {
this.name = name;
this.age = age;
this.attrObj = {
type: 'animal'
};
}
Animal.prototype.eat = function() {
console.log(this.name + '在吃东西');
}
// 子类
function Cat(name, age, sex) {
this.sex = sex;
Animal.call(this, name, age); // 利用call改变this指向
}
// 与组合式继承的区别在于将Cat.prototype = new Animal(); 替换成 Cat.prototype = Object.create(Animal.prototype);
// Cat.prototype = Object.create(Animal.prototype)的意义为: Cat.prototype.__proto__ === Animal.prototype
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
let tom = new Cat('tom', 3, 'boy');
tom.eat(); // tom在吃东西
tom.attrObj.type = '波斯猫';
let kate = new Cat('kate', 2, 'girl');
console.log('kate.attrObj.type: ', kate.attrObj.type); // animal
new
关键字创建的对象与Object.create(proto, propertiesObject)
的区别
- new Object()生成的对象会继承原型上的方法和属性并且会继承构造函数的本地的属性。
- Object.create(proto, propertiesObject)只会继承原型链上的方法和属性(参数
proto
:是新创建对象的原型对象,必填。)
let Person = function() {};
let p1 = new Person(); // 生成实例对象
p1.__proto__ === Person.prototype; // true
// 当我们不希望执行构造函数,而只需要继承父类的原型上的方法和属性时
let p2 = Object.create(Person.prototype);
p2.__proto__ === Person.prototype; // true