再见js原型

1,141 阅读4分钟

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中调用查找。

直接上图

还有一张,有助于理解