原型、原型链、及拓展(ES5的继承模式)

2021-06-22  本文已影响0人  青城墨阕

原型

  1. 概念:
let Person = function() {};
// 1. 每个函数都偶有一个属性prototype
console.log(Person.prototype); // 空对象 ------> 原型对象(隐式原型对象)
let person1 = new Person(); // 生成实例对象
// 2. 每个实例对象身上都有一个属性__proto__,该属性指向当前实例对象的原型对象(隐形原型对象)
// 3. 构造函数的显示原型对象 === 当前构造函数实例对象的隐式原型对象
Person.prototype === person1.__proto__; // true
  1. 对象:

原型链

a.b
// 找a会沿着 作用域 找,找b(对象的属性)会沿着原型链找
原型链图解.png

原型拓展

拓展一:instanceof运算符

instanceof是如何判断的?

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

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的原型链
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当前的原型链
4. 寄生式组合继承

解决构造函数被执行两次的问题, 我们将指向父类实例改为指向父类原型, 减去一次构造函数的执行。

// 父类
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)的区别
let Person = function() {};
let p1 = new Person(); // 生成实例对象
p1.__proto__ === Person.prototype; // true

// 当我们不希望执行构造函数,而只需要继承父类的原型上的方法和属性时
let p2 = Object.create(Person.prototype);
p2.__proto__ === Person.prototype; // true
上一篇下一篇

猜你喜欢

热点阅读