这块知识点比较抽象,我会尽量用代码+示意图的方式梳理。如果你有疑惑,请耐心看完,可能会有帮助。
构造函数和普通函数
function Person() { }
let zhangsan = new Person()
这块代码创建了一个Person构造函数和一个由Person构造函数创建的zhangsan实例对象。
其实Person构造函数和普通的函数并没有什么区别,只是在使用的时候用法不同,会有不同的叫法。
普通函数在进行new操作的时候,它就称为构造函数,否则它就是普通函数。
另外一点,为了在视觉上便于区分,通常会在定义当做构造函数使用的普通函数时,函数名的首字母用大写。这个不用太纠结。
prototype和__ proto __
prototype
在函数中有一个prototype属性,为方便区分,通常叫做显式原型对象。
而实例对象则没有。
这个属性比较简单了,它就是一个对象{}(new Object()),但是这个对象下包含着一个指向函数本身的constructor方法。这个对象是可以随意更改的,尤其在实现继承的时候,注意改变函数的prototype指向。
function Person(){
// prototype: { constructor: function Person() }
}
let zhangsan = new Person()
console.log(Person.prototype.constructor === Person) // true Person有prototype,且Person.prototype.constructor 指向函数本身
console.log(zhangsan.prototype) // undefined 实例对象没有prototype
__ proto __
function Person() { }
let zhangsan = new Person()
console.dir(Person.__proto__) // {...}
console.dir(zhangsan.__proto__) // {...}
无论是Person构造函数还是由该函数创建的zhangsan实例对象都会有一个叫做"__ proto __"的属性。它们是怎么来的呢?
函数/实例在创建的时候都会有一个__ proto __属性,它是由创建该函数/实例对象的父级构造函数的prototype赋值而来的。
为了方便区分,__ proto __通常叫做隐式原型
zhangsan是由Person函数创建出来的,zhangsan的父级构造函数就是Person。
那Person是由谁创建的呢,Person的父级构造函数是谁呢?
其实函数也是有父级构造函数创建出来的,这个父级构造函数就是Function。Function是一个系统内置的函数。
很多系统内置的函数也是由它创建的,如Array()、String()、Boolean()、Date()、Object()等等
// function Person(){} ==> var Person = new Function()
// var str = '123' ==> var str = new String('123')
// var obj = {} ==> var obj = new Object()
// ......
var Person = new Function()
let zhangsan = new Person()
// 这样看是不是就很像了呢。
console.dir(Person.__proto__ === Function.prototype) // true
console.dir(zhangsan.__proto__ === Person.prototype) // true
// 先就__proto__讨论,上面就发生了类似于下面的过程
// 创建Person时
// function Person() {
// __proto__: Function.prototype
// }
// new Person时
// function Person() {
// var this = {
// __proto__: Person.prototype
// }
// return this
// }
上面关于prototype 和 __ proto __ 的来龙去脉说完了,我觉得是有必要在前面就把它们讲清楚,因为原型和原型链就是围绕着这两个属性展开的。
原型链
function Person() { }
Person.prototype.job = 'student'
let zhangsan = new Person()
console.log(zhangsan.job) // student
console.log(zhangsan) // {__proto: {job:student}}
console.log(zhangsan.toString()) // "[object Object]"
从打印结果可以看出,zhangsan自身并没有job这个属性,倒是只有一个__ proto __ 属性,但是为什么zhangsan能访问到job呢?
对象访问变量的时候会首先从自身找,如果自身找不到就从自身的__ proto __ (隐式原型)属性指向的父级函数的prototype(显式原型)中寻找,如果还找不到就沿着该显示原型的__ proto __继续寻找,形成的这个链状关系就是原型链,直到到达原型链的顶端Object.prototype(null)。
示例中,zhangsan.__ proto __ 是在new Person创建zhangsan的时候,由Person的prototype赋值而来
// new Person时
// function Person() {
// var this = {
// __proto__: Person.prototype
// }
// return this
// }
zhangsan.__ proto __ 下面是有一个job属性的,所以zhangsan能访问job
zhangsan.job(no value) ==> zhangsan.__proto__ .job ==> Person.prototype.job (has value) ==> success
对照此图会更容易理解
aaa.png这个图看似复杂,其实记住根本的两句话,并不难理解。
函数和实例对象都会有一个__ proto __,它们的来源其实很简单
谁创建了函数,谁就得把自己的prototype属性赋给新创建函数的 __ proto __
谁创建了实例对象,谁就得把自己的prototype属性赋给新对象的__ proto __
还以上面的代码举例,
Person创建了zhangsan,Person的prototype就得赋给zhangsan.__ proto __ ,所以zhangsan.__ proto __ === Person.prototype
Function创建了Person,Function的prototype就得赋给Person.__ proto ,所以Person. proto __ === Function.prototype
Person.prototype也是一个对象(类似{}),谁创建了它呢,Object创建了它(new Object()),那么Person.prototype.__ proto __ === Object.prototype
说了这么多,感觉还是苍白无力,这块比较抽象,需要多实践对比,自己画画图理解一下会好很好。
个人总结,如有错误,请指正。
网友评论