MDN解释:
JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。
方方解释:
所有对象都有 toString 和 valueOf 属性,那么我们是否有必要给每个对象一个 toString 和 valueOf 呢?
明显不需要。
JS 的做法是把 toString 和 valueOf 放在一个对象里(暂且叫做公用属性组成的对象)
然后让每一个对象的 __ proto __ 存储这个「公用属性组成的对象」的地址。
原型就是共有属性
公用属性(原型)
用String赋值的变量s1,她的__ proto __ 指向了String共有属性(原型),String的原型里面还有 __ proto __的原型指向Object共有属性,Object共有属性最后指向了null,结构图如下:
- Numer的共有属性:Number.prototype
- Object的共有属性:Object.prototype
- String的共有属性:String.prototype
- Boolean的共有属性:Boolean.prototype
上面的代码Object.prototype就是上边结构图的Object的共有属性(原型),
o1的__ proto __指向Object里的原型,所以是true
n1的下划线原型指向的是Number的原型,然后Number原型里面的下划线原型指向Object的原型,所以就可以写成
n1.__proto__.__proto__ === Object.prototype
或者:Number.prototype.__proto__ === Object.prototype
注意:prototype是浏览器自动生成的,你就算不写代码也有
上面的图当你什么代码也不写的时候,打开浏览器也会有一个window对象,而window对象里有Number、Object、String、Boolean这几个全局函数,又因为函数也是对象,所以他们再次将内容存放到一个堆里,又因为是函数所以有prototype的原型,分别指向自己对应的类型,而最后prototype也是一个对象,只要是对象就有__ proto __这个下划线的原型,通过他指向了Object公用属性。就拿Number这个全局函数来举例
上面没有执行任何操作,直接打印了Number这个函数,它里面的prototype就指向了Number的公用属性,而她的prototype下面还有一个下划线的原型指向了Object的公用属性。
上面的代码,var 后面的肯定是对象,new后面的是函数对象
对象的下划线原型指向该实例对象的构造函数的原型也就是说:
var n = new Object();
n.__proto__ === Object.prototype
//new后面的就是构造函数名,以new方法生成的函数就是构造函数
Object.prototype.proto ===null
对象由构造函数创建的
数组、函数、正则都是对象
对象的proto等于创建者的prototype
prototype上有个属性constructor 指向创建者(构造函数)
数组的构造函数是 Array
函数的构造函数是 Function(当构造函数作为对象时,这个构造函数的构造函数就是Function)
正则的构造函数是 RegExp
而String,Number,Boolean,Object,Function这几个是全局函数,所以它的构造函数就是Function,故指向Function.prototype:
这里最需要注意的是Function的构造函数就是Function,只有它的proto是指向自己的:
另外自己写的构造函数也是一样的,当构造函数作为对象时.__ proto __===Function.prototype
如:
function Person(name){
this.name = name;
}
var person = new Person('wang')
Person.__proto__===Function.prototype //true
//因为Person是一个函数,函数也是一个对象所以有__proto__的属性,而函数的构造函数是Function
另外判断一个对象的构造函数可以用
1.console.log(xxx.constructor),但是注意xxx只能是单独的对象就是xxx里面不可以接prototype或者__ proto __
第一个Function.constructor是可以正确判断的,但Function.prototype.constructor就不能,它显示的也是Function可实际上它的构造函数是Object。另外判断一个构造函数是什么其实上述方法并不可取,因为不排除有人为改变构造函数的可能,所以暂且只需记住就行,另外只要是(对象.prototype)或者(对象.__ proto __)作为对象的时候他们的构造函数都是Object(这里排除特例)
2.实例对象.constructor(构造器里的prototype始终指向构造函数本身)
实例对象的proto和构造函数中的prototype相等--->true
因为实例对象是通过构造函数来创建的,构造函数中有原型对象prototype
实例对象的proto指向了构造函数的原型对象prototype
只要是对象就有proto原型
又因为prototype也是对象,所以,prototype也有proto
原型(对象)、构造函数、实例、原型链
关系图如下:
1.构造函数:就是函数,名字首字母应该大写
function Foo(name,age){
this.name = name
this.age = age
this.class = 'class-1'
// return this // 默认有这行
}
2.实例对象:new 函数() => 实例
// 创建多个实例对象
var f1 = new Foo('zhangsan',21)// new 就是实例化的过程
var f2 = new Foo('lisi',22)
var f3 = new Foo('wangwu',23)
实例对象.__proto__ === 构造函数.prototype
实例对象.proto === 构造函数.prototype
描述 new 一个对象的过程
- 创建一个新对象,它继承自构造函数Foo.prototype
- 执行构造函数Foo。执行时,相应的参数会被传入,同时上下文 this,指向这个新实例。在不传递参数时,new Foo等同于new Foo()
- 执行代码,即对 this 赋值(this.key = value)
- 构造函数 Foo,return一个对象,那么这个对象会取代整个 new 出来的结果。如果构造函数没有return对象,那么 new 出来的结果,就是步骤1创建的新对象
3.原型对象:prototype,每个函数都有这个属性,也是用于扩展构造函数的
Foo.prototype.alertName = function(){
alert(this.name)
}
f1.printName = function(){
console.log(this.name)
}
f1.printName()
f1.alertName()
构造器:constructor
在原型对象(prototype)下有个constructor属性,它的作用是指向声明的那个函数(就是构造函数)
Foo.prototype.constructor === Foo返回true
原型5条规则:
- 所有的引用类型(对象、函数、数组),都具有对象特性,即可自由扩展属性(除null以外)
- 所有的引用类型(对象、函数、数组),都有一个__ proto __属性,属性值是一个普通对象(除null以外)—— 隐式原型
- 所有的函数都有一个prototype属性,属性值是一个普通对象 —— 显示原型
- 所有的引用类型(对象、函数、数组),__ proto __属性值指向(全等)它的构造函数的prototype属性值
- 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__ proto __中寻找,(也就是去它的构造函数的 prototype)
4.原型链
var obj = { name: 'obj' }
在我们没对obj进行任何操作之前,obj就已经有toString()方法,那么toString()方法是怎么来的哪?
答:这跟 proto 有关。
当我们「读取」 obj.toString 时,JS 引擎会做下面的事情:
- 看看 obj 对象本身有没有 toString 属性。没有就走到下一步。
- 看看 obj.__ proto __ 对象有没有 toString 属性,发现 obj.__ proto __ 有 toString 属性,于是找到了,就会退出
所以 obj.toString 实际上就是第 2 步中找到的 obj.__ proto __.toString。
可以想象,- 如果 obj.__ proto __ 没有,那么浏览器会继续查看 obj.__ proto . proto __
- 如果 obj.__ proto . proto __ 也没有,那么浏览器会继续查看 obj.__ proto . proto . proto __
- 直到找到 toString 或者 __ proto __ 为 null。
上面的过程,就是「读」属性的「搜索过程」。
而这个「搜索过程」,是连着由 __ proto __ 组成的链子一直走的。
这个链子就叫原型链。
通过下图我们可以更好的理解:
典型例题:
function Fn(){
this.a = function(){
console.log(1)
}
}
Fn.prototype.a = function(){
console.log(2)
}
new Fn().a() //1
上面的代码先从实例对象本身开始搜索有没有a这个方法,因为本身就有所以直接退出搜索
参考文章:什么是 JS 原型链?