JS笔记《原型与原型链》

83 阅读6分钟

prototype属性

  • JS实现继承的设计思想是,原型对象的所有属性和方法都能被实例对象共享。prototype是函数的一个属性,对于普通函数来说基本无用。但是对于构造函数来说,生成实例时,该属性会自动成为实例对象的原型对象。
function Person(){
    
}
Person.prototype.lastName = 'zhangsan';

var person = new Person();
var person1 = new Person();

console.log(person.lastName);   // 'zhangsan'   person对象没有lastName属性,所以会向原型对象上查找
console.log(person1.lastName);  // 'zhangsan'
  • 把那些不变的、共有的属性和方法提取出来,直接定义到 prototype上,这样所有对象实例可以共享这些属性和方法。
function Person(name){
    this.name = name;
}
//共有属性
Person.prototype.sex = '男';
//共有方法
Person.prototype.say = function(){
    console.log('我会说话');
}
 
var person = new Person('peter');
var person1 = new Person('bob ');

console.log(person.sex);   // '男'
console.log(person1.sex);  // '男'
console.log(person.say());  // '我会说话'
console.log(person1.say());  // '我会说话'

如果把同一类对象相同的属性和方法都定义为构造函数的实例成员,则每次实例化对象时都会在内存中新开辟空间,造成内存浪费。

__proto__属性

  • 每个对象都有一个__proto__属性指向它的原型对象(构造函数的prototype),该属性可读写。
  • 此属性本质上是一个内部属性,只有浏览器才有这个属性。不建议使用,而是用getPrototypeOfsetPrototypeOf替代。
var obj = {};
var p = {};

obj.__proto__ = p;
Object.getPrototypeOf(obj) === p // true

原型链

  • 所有对象都有自己的原型对象prototype,任何一个对象都可以充当其他对象的原型。由于原型对象也是对象,所以它也有自己的原型,因此就会形成一个原型链
  • 读取对象的某个属性时,JS会首先寻找对象自身的属性,如果找不到,就去它的原型去找,如果还是找不到,就去原型的原型去找,直到找到Object.prototype还是找不到,则返回undefined
  • 如果构造函数中实例成员覆盖了原型上的属性或方法,则以实例成员的结果为准(就近原则)。
function Father(name, hobbit) {
    this.name = name;
    this.hobbit = hobbit;
}
Father.prototype.sex = '男';
var father = new Father('james', 'smoke');

function Son(name) {
    this.name = name;
}
Son.prototype = father; // father对象实例指向了Son的原型
var son = new Son('peter');
console.log(son.name);  // 'peter' 
// son对象中有 name属性。停止查找

console.log(son.hobbit);  // 'smoke'
// son对象中没有 hobbit属性,沿着__proto__向上查找: 
// 1. son的__proto__指向 Son.prototype
// 2. Son.prototype指向 father对象
// 3. father对象中有 hobbit属性,停止查找

console.log(son.sex);   // '男'
// son对象中没有 sex属性,沿着__proto__向上查找: 
// 1. son的__proto__指向 Son.prototype
// 2. Son.prototype指向 father对象
// 3. father对象中没有 sex属性,沿着__proto__继续向上查找
// 4. father的__proto__指向 Father.prototype
// 5. Father.prototype中有 sex属性,停止查找

console.log(son.age);   // undefined
// son对象中没有 age属性,沿着__proto__向上查找: 
// 1. son的__proto__指向 Son.prototype
// 2. Son.prototype指向 father对象
// 3. father对象中没有 age属性,沿着__proto__继续向上查找
// 4. father的__proto__指向 Father.prototype
// 5. Father.prototype中没有 age属性,沿着__proto__继续向上查找
// 6. Father.prototype中__proto__指向 Object.prototype
// 7. Object.prototype中没有__proto__对象,表示已经到达了原型链的终点,依然没找到,返回 null
// 8. 最终结果,返回 undefined

Function 和 Object

  • 所有函数都是Function构造函数创建的实例对象。
  • Object函数本身也是Function的实例对象,但Object.prototypeObject构造函数的实例对象。
function Test(){}

var test = new Test();              // test对象是由 Test构造方法构造出来的
console.log(test.__proto__ === Test.prototype);  // true 

// var Test = new Function(...);    // function Test(){}是由 Function构造方法构造出来的
console.log(Test.__proto__ === Function.prototype);     // true __proto__指向构造函数的原型
console.log(Function.__proto__ === Function.prototype); // true 底层规定

const obj = {};             // 等价于 const obj = new Object();
console.log(typeof Object); // "function"   Object 又是由 Function 构造出来的
console.log(Object.__proto__ === Function.prototype);   // true __proto__指向构造函数的原型
console.log(Object.__proto__ == Function.__proto__);    // true

图解原型链

image.png

constructor

  • constructor是prototype对象的一个属性,默认指向prototype对象所在的构造函数。由于constructor属性定义在prototype对象上,所以此属性可以被所有实例对象继承。
function P(){}
var p = new P();

p.constructor === P   // true
p.constructor === P.prototype.constructor  // true  constructor属性被实例对象继承

constructor属性的作用是,可以得知某个实例对象到底是哪一个构造函数产生的。

  • constructor属性表示原型对象与构造函数之间的关联关系,如果修改了原型对象,需要同时修改constructor属性,防止引用时候报错。
function F(){}
function F2(){}
console.log(F.prototype.constructor);  // F(){}

// 将构造函数F的原型对象修改为F2的实例对象
F.prototype = new F2()
// 通过F构造函数创建一个实例对象
var f = new F();
console.log(f.constructor)   // F2(){}  指向错误 constructor指向了F2,表示f实例对象是F2构造函数产生的
// 修改constoructor指向
F.prototype.constructor = F;
console.log(f.constructor)  // F(){}  正确

instanceof 运算符

  • A instanceOf B,返回一个boolean值,表示对象是否为某个构造函数的实例。原理是检查A对象的原型链上能否找到B构造函数的prototype
function Person(){}
var p = new Person()  

p instanceof Person  // true  p.__proto__ === Person.prototype
p instanceof Object  // true  p.__proto__ => Person.prototype.__proto__ === Object.prototype

// 对于undefined 和 null总是返回 false
undefined instanceof Object // false
null instanceof Object      // false

原型相关方法

Object.getPrototypeOf()

  • 返回参数对象的原型
function F(){}
var f = new F();
Object.getPrototypeOf(f) === F.prototype  // true
Object.getPrototypeOf(F) === Function.prototype  // true

Object.getPrototypeOf({}) === Object.prototype // true 
Object.getPrototypeOf(Object.prototype) === null  // true

Object.setPrototypeOf()

  • 为参数对象设置原型,返回该参数对象。此方法接受两个参数,参数一为要设置原型的对象,参数二为原型对象
var a = {}
var b = {x: 1}

Object.setPrototypeOf(a, b); // 将 a 对象的原型对象设置为 b
a.x // 1  a 可以使用 b 的属性
Object.getPrototypeOf(a) === b  // true

Object.create()

  • 接收两个参数,参数一接收一个对象作为参数,然后以它为原型,返回一个实例对象。参数一不能为空或者不是对象,否则就会报错。
var a = {
  print: function () {
    console.log('hello');
  }
};
var b = Object.create(a);
Object.getPrototypeOf(b) === a // true
b.print()  // 'hello'

Object.create() // Uncaught TypeError: Object prototype may only be an Object or null
Object.create(123) // Uncaught TypeError: Object prototype may only be an Object or null
  • 如果想要生成一个不继承任何属性的对象,可以将参数设置为null
var obj = Object.create(null);
Object.getPrototypeOf(obj); // null
  • 参数二是一个属性描述对象,它所描述的对象属性会添加到实例对象上。
var obj = Object.create({}, {
  p1: {
    value: 123,
    enumerable: true,
    configurable: true,
    writable: true,
  },
  p2: {
    value: 'abc',
    enumerable: true,
    configurable: true,
    writable: true,
  }
});

obj // {p1: 123, p2: 'abc'}

Object.prototype.isPrototypeOf()

  • 判断该对象是否为参数对象的原型,只要该对象处在参数对象的原型链上,都返回true
var o1 = {};
var o2 = Object.create(o1);
var o3 = Object.create(o2);
o1.isPrototypeOf(o2); // true o2是以o1为原型创建的实例对象,所以o1是o2的原型
o1.isPrototypeOf(o3); // true o1和o2都是o3对象的原型

Object.prototype.isPrototypeOf({}) // true
Object.prototype.isPrototypeOf([]) // true
Object.prototype.isPrototypeOf(/xyz/) // true
Object.prototype.isPrototypeOf(Object.create(null)) // false
// Object.prototype处于原型链的最顶端,所以对各种实例对象都返回true,除了继承自null的实例对象