js中的原型和原型链是难点也是重点,在前端的行进的道路上是绕不开也是必过的一道坎。
花费几分钟看完此文,和原型说拜拜。
MDN是这样描述的:
当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为
null。根据定义,null没有原型,并作为这个原型链中的最后一个环节。
几乎所有 JavaScript 中的对象都是位于原型链顶端的
Object的实例。
通彻原型首先记住以下几点:
1. 所有的引用类型(函数,对象,数组)都拥有__proto__属性(隐式原型),指向它的构造函数的原型对象;
2. 所有函数拥有prototype属性(显式原型),这个属性指向原型对象;
3. 原型对象是一个拥有prototype属性的对象;
4. 函数在创建时,系统就会自动分配一个prototype属性;
5. 原型对象默认都有一个constructor属性,指向它的那个构造函数。
可以在浏览器的console面板中运行以下代码:
function doSomething(){}
console.log( doSomething.prototype );
// 和声明函数的方式无关,
// JavaScript 中的函数永远有一个默认原型属性。
var doSomething = function(){};
console.log( doSomething.prototype );
可以看到 doSomething函数有:constructor 和 __proto__ 属性;
接下来 我们给doSomething的原型对象添加属性 foo
doSomething.prototype.foo = "bar"
console.log(doSomething.prototype)
然后再次在控制台输出doSomething的原型对象,可以看到
接着 我们new一个实例出来,并给它添加个prop属性,在控制台输出,可以看到:
从图中可以明显的看出:doSomeInstancing.__propto === doSomething.prototype 那么原型链它来了
当你访问doSomeInstancing 中的一个属性,浏览器首先会查看doSomeInstancing 中是否存在这个属性。
如果 doSomeInstancing 不包含属性信息, 那么浏览器会在 doSomeInstancing 的 __proto__ 中进行查找(同 doSomething.prototype). 如属性在 doSomeInstancing 的 __proto__ 中查找到,则使用 doSomeInstancing 中 __proto__ 的属性。
否则,如果 doSomeInstancing 中 __proto__ 不具有该属性,则检查doSomeInstancing 的 __proto__ 的 __proto__ 是否具有该属性。默认情况下,任何函数的原型属性 __proto__ 都是 window.Object.prototype. 因此, 通过doSomeInstancing 的 __proto__ 的 __proto__ ( 同 doSomething.prototype 的 __proto__ (同 Object.prototype)) 来查找要搜索的属性。
如果属性不存在 doSomeInstancing 的 __proto__ 的 __proto__ 中, 那么就会在doSomeInstancing 的 __proto__ 的 __proto__ 的 __proto__ 中查找。然而, 这里存在个问题:doSomeInstancing 的 __proto__ 的 __proto__ 的 __proto__ 其实不存在。因此,只有这样,在 __proto__ 的整个原型链被查看之后,这里没有更多的 __proto__ , 浏览器断言该属性不存在,并给出属性值为 undefined 的结论。
可以进一下在控制中对上述 进行验证:如下代码
function doSomething(){}
doSomething.prototype.foo = "bar";
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value";
console.log("doSomeInstancing.prop: " + doSomeInstancing.prop);
console.log("doSomeInstancing.foo: " + doSomeInstancing.foo);
console.log("doSomething.prop: " + doSomething.prop);
console.log("doSomething.foo: " + doSomething.foo);
console.log("doSomething.prototype.prop: " + doSomething.prototype.prop);
console.log("doSomething.prototype.foo: " + doSomething.prototype.foo);
结果如下所示:
举个栗子
function Person(){}; //构造函数
var p = new Person(); // new一个实例对象p
p.__proto__ === Person.protoype; // true, 说明实例对象p的__proto__属性指向了它的原型对象
Person.prototype.constructor === Person // true, 说明原型对象默认的constructor属性,指向了指向它的那个构造函数
既然原型也是对象,那么就意味着可以重写这个对象。
function Person() {}
// 先重写原型
Person.prototype = {
name: 'tom',
age: 18,
sayHi() {
console.log('Hi');
}
}
// 再实例化对象
var p = new Person()
但是在重写的时候需要注意:
function Person(){}
// 先实例化对象
var p = new Person();
// 再重写
Person.prototype = {
name: 'tom',
age: 18
}
Person.prototype.constructor === Person // false
Person.prototype.constructor === Object // truep.name // undefined
重写原型对象,会导致原型对象的constructor属性指向Object,导致原型链关系混乱,所有应该在重写原型对象的时候要指定constructor
Person.prototype = {
constructor: Person
}
注意:以这种方式重设 constructor 属性会导致它的 Enumerable 特性被设置成 true(默认为false)
原型链
所有原型链的终点都是Object函数的prototype属性;
Object.prototype指向的原型对象同样拥有原型,它的原型是mull,而null没有原型;
原型链是由__proto__属性连接而成。
当调用某个方法或者查找某个属性时,首先会在自身中调用和查找,如果自身没有,则会沿着__proto__属性的指向去调用查找,也就是在它的构造函数的prototype中调用查找。
直接上图
还有一张,有助于理解