如何理解JavaScript中的原型和原型链
本文基于理解基础知识进行的拓展,并不会包含太多的基础知识。
首先是一张关系图,避免抽象化理解时产生的困难
Function对象
函数对象是JavaScript学习中不可避免的一部分,而且这一部分相对重要且抽象
函数的创建方式有2种:
- 字面量创建
var foo = function(){
console.log("test");
}
- new 关键字创建实例对象
//let 函数名 = new Function(“参数列表”,”函数体”);
let sum = new Function("num1,num2","return num1+num2");
我们多数在使用new关键字的时候,是用于创建实例对象
那么我们首先来看一下,在使用new关键字创建实例对象的时候,都经历了什么:
- 创建一个新对象
- 链接到原型对象,继承属性和方法
- 将构造函数的作用域赋给新对象(this指向改变到实例对象中)
- 返回新对象
function create() {
// 创建一个空的对象
let obj = new Object()
// 获得构造函数
let Con = [].shift.call(arguments)
// 链接到原型
obj.__proto__ = Con.prototype
// 绑定 this,执行构造函数
let result = Con.apply(obj, arguments)
// 确保 new 出来的是个对象
return typeof result === 'object' ? result : obj
}
这个时候我们的关注点 原型对象 就出现了:
原型对象prototype
prototype
是一个显式原型属性(也可以叫它原型对象),只有函数才有该属性,通常我们叫这个时候的函数为"构造函数"prototype
的伴随构造函数的声明就会被自动创建- 原型对象
prototype
只有一个属性:constructor
代码举例:
function Student(name,age){
this.name = name;
this.age = age;
}
let s1 = new Student("Tom",17);
首先我们创建了一个构造函数Student
此时Student
的结构中会出现一个prototype
属性,即原型对象,这是引擎自动给它的,我们可以直接进行使用
构造函数prototype
中的constructor
属性:
此时可以看出constructor
对应的是构造函数,也就是Student
并且这是一个公有不可枚举属性,一旦改变了prototype
,这个属性就会不见,当然可以再手动添加回去
而当我们再使用new关键字创建实例对象s1
之后,我们来看一下s1
的结构:
实例对象s1
中除了在Student
获得的age
,name
属性之外,还有一个__proto__
属性,所以它又是什么东西呢?
__proto__
是什么
__proto__
是每个对象都有的隐式原型属性,指向了创建该对象的构造函数的原型对象prototype
,但是 prototype
是内部私有属性,我们并不能访问到,所以使用__proto__
进行访问
至于__proto__
是如何产生的,上面的new关键字创建函数的时候的第三部"链接到原型,继承属性和方法"的时候就让实例对象,例如s1
拥有了__proto__
属性
从实例对象s1
的__proto__
指向构造函数Student
的prototype
,构成了原型链
通过原型链的概念,我们就不难理解实例对象是如何继承构造函数中原型对象的属性和方法了
function Student(name,age){
this.name = name;
this.age = age;
}
Student.prototype.method = function(){
console.log("我的名字是"+this.name+",我的年龄是"+this.age);
}
let s1 = new Student("Tom",17);
s1.method(); //我的名字是Tom,我的年龄是17
深思考:底层的函数是如何创建出来的
从第一张关系图中以及后面的说明,我们可以知道Function.__proto__ === Function.prototype
是成立的,并且Function.prototype
和Function.__proto__
都指向Function.prototype
那么Function
到底是谁创建出来的呢?是它自己创建了自己吗?
并不是
在解释这个问题前,我们要先说一下Object
JavaScript中所有对象都可以通过原型链一层一层的最终找到 Object.prototype
(原型链顶端),虽然 Object.prototype
也是一个对象,但是这个对象却不是 Object
创造的,而是JavaScript引擎自己创建了 Object.prototype
所以可以推论出 所有实例都是对象,但是对象不一定都是实例
紧接着再说回Function
的创建问题
在控制台打印一下Function
和Function.prototype
在JavaScript引擎首先创建了 Object.prototype
之后 ,又创建了 Function.prototype
,并且通过 __proto__
将两者联系了起来
既然Function.prototype
是JavaScript引擎创建出来的对象,所以就没有"到底是先有鸡还是先有蛋"这样的悖论问题出现
同样可以推导出:并不是所有的实例对象(函数)都是通过 new Function
方式创建出来的
这是一种比较严谨的说法,因为在通过字面量创建函数的时候就不会调用 函数构造器,也就是说它并不是new
出来的,也就不存在这样的情况
至于说为什么Function.__proto__ === Function.prototype
,有种说法是为了不产生混乱,就把二者链接在了一起
在JavaScript的函数和对象的学习中,剖析其中关系网时,真的会让人上瘾,想要一层一层的去搞清楚弄明白,而且只有在理清了其中每一层的对应关系时才会对它们有一个更加清晰的认知,在使用的时候也会更加的自信,解决开发问题的时候更加的灵活和优雅 so, let's hacking