原型
原型对象
原型对象获取:函数的prototype属性
- 每个函数都有一个prototype属性,它默认指向一个
Object空对象
,即称为:原型对象
eg:
function fun (){
}
console.log(fun.prototype,typeof fun.prototype)
虽然prototype属性默认指向一个Object空对象
,没有属性和方法,但是原型还有原型,那里面有共有的属性和方法,后面会讲解。
给原型对象添加属性(一般都是方法)
可以向prototype指向的Object空对象中添加方法和属性
function fun (){
}
fun.prototype.test = function (){
console.log('我是添加的函数')
}
console.log(fun.prototype,typeof fun.prototype)
eg:Date的原型
console.log(Date.prototype,typeof Date.prototype)
原型对象和函数对象的关系
原型对象中有一个属性constructor它指向 函数对象
function fun (){
}
console.log(fun.prototype.constructor === fun)
console.log(Date.prototype.constructor === Date)
所以原型对象和函数对象可以通过自己的属性找到对方:
prototype属性 和 constructor 属性一一对应
函数对象.prototype = 原型对象
原型对象.constructor = 函数对象
如果没有prototype属性就不会有对应的constructor属性
eg:末尾的实例对象没有prototype 属性,自然也不会有对应的constructor指向它。
原型对象的作用
原型对象一般用于构造函数。
原因:构造函数可以创建实例,而所有实例对象自动拥有该构造函数原型中的属性和方法,这就相当于所有实例的公共区域,所以使用原型对象可以很方便地为所有实例添加共有的属性和方法。
eg:
function Fun (){
}
Fun.prototype.test = function (){
console.log("我是共有的方法")
}
var fun =new Fun()
fun.test()
输出:
我是共有的方法
显示原型与隐式原型
基本含义
-
显式原型:每个(构造)函数function都有一个prototype,即显式原型(属性),默认指向一个空的0bject对象
-
隐式原型:每个实例对象都有一个__proto__,可称为隐式原型(属性)
-
显示原型与隐式原型的关系
实例对象的隐式原型 = 构造函数的显式原型
即:实例对象.__proto__ = 构造函数.prototype
__proto__是实例对象的属性,prototype是构造函数的属性。 -
区分:
函数的prototype属性:在定义函数时自动添加的,默认值是一个空object对象。
对应的内部语句是:this.prototype = {}
对象的_proto_属性:创建对象时自动添加的,默认值为构造函数的prototype属性值
对应的内部语句是:this.__proto__ = Fn.prototype
function Fn (){
}
// 1.每个函数function都有一个prototype,即显式原型(属性),默认指向一个空的0bject对象
console.log(Fn.prototype)
// 2.每个实例对象都有一个__proto__,可称为隐式原型(属性)
var fn = new Fn()
console.log(fn.__proto__)
// 3.对象的隐式原型的值为其对应构造函数的显式原型的值
console.log(Fn.prototype === fn.__proto__)
输出:
内部结构
通过实例调用原型的方法
当我们使用一个对象的属性或方法时,会先在自身中寻找该属性或方法,自身中如果有,则直接使用,如果没有则去原型对象中寻找。
所以如果通过函数的prototype属性为原型对象添加的属性或方法,可以通过实例对象直接访问。
eg:
function Fn (){
}
var fn = new Fn()
Fn.prototype.test =function(){
console.log("hello")
}
fn.test();
输出:
hello
注意: 程序员能直接操作显式原型,但不能直接操作隐式原型(ES6之前)
所以我们经常使用 显式原型 添加属性
,使用 隐式原型 访问属性
。
原型链
我们一直说函数的原型是一个Object空对象,从名字可以看出它是一个Object
构造函数的实例。
既然是实例就应该有隐式原型__proto__
属性,也有对应的构造函数Object,同时该构造函数有对应的原型对象。
图解:
原型链的尽头就是Object的原型对象。
始终遵守实例对象的隐式原型 = 构造函数的显式原型
根据原型链查找对象属性
原型链的作用:查找对象的属性或方法
而查找对象的属性或方法的时候一般都是使用隐式原型链(__proto__
)进行查找,所以原型链一般又叫做隐式是原型链。
对象属性的查找过程
- 先在自身属性中查找,找到返回
- 如果没有,再沿着
_proto__
这条链向上查找,找到返回 - 知道查找到Object的原型对象,如果最终没找到,返回undefined
函数的原型链——带图解
前面我们以构造函数为原型链的开头,但是构造函数也是通过new
得来的:
即:
function fun(){
}
相当于
var fun = new function(){
}
如果这样看的话 Function() 应该是fun的构造函数,所以fun
函数也应该有原型函数。
所以原型链是如下:
按照上面说的,你是不是会想那 Function()还有没有构造函数呢,它的__proto__
再指向它的构造函数的原型对象。答案是有的,Function还有构造函数,但是它的构造函数是他自己,即
Function = new Function()
再根据 实例对象的隐式原型 = 构造函数的显式原型 原则,那么 Function 的__proto__
和prototype
指向同一个原型对象:
任何函数都是通过 new Function() 创建的,无论是内置的函数还是我们自己创建的函数,所以Object()函数也是由new Function() 创建的,它的__proto__属性指向Function原型函数。
Function原型对象又是 Object函数 的实例,验证:
// A instanceof B:就是验证 A是不是B的实例
console.log(Function.prototype instanceof Object)
输出:
所以Function原型函数的 __proto__属性 指向 Object的原型对象
也正式这样使得函数有许多从 Object圆形函数中继承来的方法、属性。所以通过函数名.属性/方法 就可以调用相应的属性/方法。
function Fn (){
}
console.log(Fn.toString())
输出:
所以说除去底端实例所有的函数都有两个属性,一个是 __proto__
,一个是prototype
,这是由于这个函数既是构造函数同时又是另一个构造函数的实例所致。
prototype
指向自己的原型对象,__proto__
指向构造函数的原型对象。
原型链小结
- 所有函数的显示原型指向的对象默认是
空object实例
对象,包括Function。
但Object不满足,Object的显示原型是自己的原型对象。
空object实例
是Object的实例对象,所以除Object外的函数的显示原型对象的隐式原型对象
才是Object原型对象。
console.log(Fn.prototype instanceof Object)
console.log(Function.prototype instanceof Object)
console.log(Object.prototype instanceof Object)
输出:
- 所有函数都是Function的实例(包含Function)
即:所有函数的构造函数是一样的,所以所有函数的隐式原型对象__proto__都是一样的,都指向Function的原型对象。
- 并且Object原型对象是原型链的终点,因为Object的原型对象的
__proto__
== null。
原型继承
构造函数的实例对象自动拥有构造函数原型对象的属性(方法),这就是利用原型链实现的原型继承。
原型——属性问题
如果在书写的时候直接写对象.属性
:
- 读取对象的属性值时:会自动到原型链中查找,从内向外逐步查找。
- 设置对象的属性值时:不会查找原型链,如果当前对象中没有此属性,直接将该属性添加到该对象上。
eg:
function Fn (){
}
Fn.prototype.a='xxx'
var fn1 = new Fn()
var fn2 = new Fn()
fn2.a = 'yyy'
console.log(fn1.a)
console.log(fn2.a)
输出:
- 方法一般定义在原型中,属性一般通过构造函数定义在对象本身上
因为不同对象的方法可能相同,但是属性一般不一致。
eg:
function Person (name,age){
this.name = name
this.age = age
}
var p1 = new Person("小李",21)
var p2 = new Person("小张",21)
Person.prototype.setName = function (name){
this.name = name;
}
p1.setName("小王")
p2.setName("小刘")
console.log(p1.name,p2.name)
console.log(p1)
instanceof 的原型理解
instanceof 的判断原理
表达式: A instanceof B
如果B函数的显式原型对象在A对象的原型链上,返回true,否则返回false。
这里的原型链指的是隐式原型链。
即判断的方法找到B的显示原型,然后找A的__proto__链上是否有该显示原型,有就返回true,否则返回false。
例1
function Foo (){
}
var f1 = new Foo()
console.log(f1 instanceof Foo)
console.log(f1 instanceof Object)
为什么说 f1 是 Object 的实例呢,看图
Foo和Object的显式原型对象都在A的原型链上,所以都返回true。
例2
console.log(Object instanceof Function)
console.log(Object instanceof Object)
console.log(Function instanceof Function)
console.log(Function instanceof Object)
function Fn(){}
console.log(Object instanceof Fn)
输出:
就是根据原型链图进行查找,这里以Object instanceof Object
为例:
Object的原型对象在Object的原型链上,所以返回true。
面试题
题1
function A (){
}
A.prototype.n = 1
var b =new A()
A.prototype = {
n:2,
m:3
}
var c = new A()
console.log(b.n,b.m,c.n,c.m)
输出:
分析:
题2
var F = function (){
}
Object.prototype.a = function (){
console.log('a()')
}
Function.prototype.b = function (){
console.log('b()')
}
var f = new F()
f.a()//a()
f.b//undefine
F.a()//a()
F.b()//b()
以F
为例:
可以看到F函数既可以找到A,又可以找到B。
补充
两种报错方式
- 查找变量,没有变量就报错 —— 作用域链
- 查找对象,没有对象就返回undefined —— 原型链
继承
- js是基于对象继承的
- 而像Java一类的语言是基于类执行的