今天看了冴羽大大的JavaScript深入之从原型到原型链的文章,现在自己根据自己的理解写下笔记,方便日后翻阅。
首先创建一个对象
function Person() {};
var xiaoming = new Person();
xiaoming.name = '小明';
console.log(xiaoming.name); // 小明
prototype(显式原型)
每一个函数都有一个prototype属性,注意:prototype是函数才会有的属性
function Person() {}
Person.prototype.name = '小明';
var xiaoming1 = new Person();
var xiaoming2 = new Person();
console.log(xiaoming1.name) // 小明
console.log(xiaoming2.name) // 小明
函数的prototype属性指向的是一个对象,这个对象就是调用该构造函数而创建的实例的原型,也就是xiaoming1和xiaoming2的原型。
原型是什么呢,就是每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型“继承”属性。
我自己的理解是:每当你创建一个对象(函数)XXX时,就会有一个XXX.prototype这样的实例原型;我们可以通过 .prototype 来访问到这个实例原型。
而这个原型的作用是可以被构造函数的实例所继承;也就是说,我们可以把一些不变的或者是公共的属性和方法,直接定义在prototype对象属性上。
可以通过下图理解构造函数和实例原型之间的关系:
_proto_(隐式原型)
这是每一个JavaScript对象(除了null)都具有的一个属性__proto__,这个属性会指向该对象的原型。
function Person() {}
var xiaoming = new Person();
console.log(xiaoming.__proto__ === Person.prototype); // true
看图可能会更加清晰一点:
这个就很好理解啦,就是使用new创建的实例对象xiaoming可以通过__proto__来访问到实例原型
constructor
constructor属性可以指向关联的构造函数,这个属性是每一个原型都会有的属性。 需要注意的是,constructor只能指向构造函数,不能指向实例,因为一个构造函数可以生成多个实例的
function Person() {}
console.log(Person === Person.prototype.constructor); // true
看图:
总结一下:
function Person() {}
var xiaoming = new Person();
console.log(xiaoming.__proto__ === Person.prototype); // true 通过__proto__可以指向该对象的原型,所以是true
console.log(Person === Person.prototype.constructor); // true 通过原型的constructor可以指向关联的构造函数,所以是true
// 顺便学习ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(xiaoming) === Person.prototype) // true
以上的几个我觉得结合看图还是比较好理解的,如果不理解,就多看几遍吧~哈哈哈
实例与对象
当读取完实例的属性时,如果找不到,就会一直往上查找域对象关联的原型中的属性,一直找到最顶层为止
function Person() {}
Person.prototype.name = 'waikam';
var xiaoming = new Person();
xiaoming.name = '小明';
console.log(xiaoming.name); // 小明
delete xiaoming.name;
console.log(xiaoming.name); // waikam
这个也很明显,当我们给xiaoming实例添加了name属性的时候,打印xiaoming.name肯定就返回“小明”了。
但是当我们将xiaoming的name属性删除之后,打印xiaoming.name由于找不到name属性,所以会从xiaoming的原型也就是xiaoming.__proto__上寻找,而xiaoming.__proto__指向的是Person.prototype,所以打印xiaoming.name 的时候会返回Person.prototype的name属性,就是waikam
原型的原型
如果我们没有给构造函数的原型添加属性的话,那继续打印xiaoming.name会怎样呢?
function Person() {}
Person.prototype.name = 'waikam';
var xiaoming = new Person();
xiaoming.name = '小明';
console.log(xiaoming.name); // 小明
delete xiaoming.name;
console.log(xiaoming.name); // waikam
Object.prototype.name = 'obj';
delete Person.prototype.name;
console.log(xiaoming.name); // obj
看上面的例子知道,原型对象是通过Object构造函数生成的,不明白的可以看图:
通俗点讲就是如果在孙子这里没找到就找爸爸,如果爸爸都没找到,就找爸爸的爸爸
原型链
既然如此,那Object.prototype的原型是不是又有原型呢?
function Person() {}
Person.prototype.name = 'waikam';
var xiaoming= new Person();
xiaoming.name = '小明';
console.log(xiaoming.name); // 小明
delete xiaoming.name;
console.log(xiaoming.name); // waikam
Object.prototype.name = 'obj';
delete Person.prototype.name;
console.log(xiaoming.name); // obj
delete Object.prototype.name;
console.log(xiaoming.name); // undefined
console.log(Object.prototype.__proto__); // null
能直接看到如果将Object原型的name属性删掉的话,你继续打印xiaoming.name会返回undefined
通过打印Object.prototype._proto_ 就会看到返回的是null,就是说Object.prototype再往上就是空了。看图:
这里要说明的是,图中由相互关联的原型组成的链状结构就是原型链了,就是红色的这条线
原型链就是红色的线啦,而原型一直往上找也只能找到爸爸的爸爸,毕竟不能无限套娃嘛对不对
最后再做几个例子,要填什么答案才为true
function Person() {}
var person1 = new Person()
console.log(person1.constructor === ) // true
console.log(person1.__proto__ === ) // true
console.log(person1.__proto__.constructor === ) // true
console.log(Person.__proto__ === ) // true
console.log(Person.constructor === ) // true
console.log(Person.prototype.constructor === ) // true
console.log(Person.prototype === ) // true