prototype
- 每个函数都有prototype属性,它指向函数的原型对象(Person.prototype)(从下面的例子中可以看出它默认指向的是object空对象,而Date构造函数会有很多方法是因为在原型中添加了方法),通过函数创建的对象也将会拥有该原型对象。
function Person() {}
let p = new Person()
console.log(Date.prototype,'---',Person.prototype)
//{constructor: ƒ Date()
//getDate: ƒ getDate()
//getDay: ƒ getDay()
//getFullYear: ƒ getFullYear()
//getHours: ƒ getHours()……} --- {constructor: ƒ}
- 这样说或许有点抽象,不如先来举个例子
var arr1 = [2,4,6,4,3]
var arr2 = [3,5,3,6,4,8,3]
arr1.sort(function(num1,num2) {
return num1 - num2
})
arr2.sort(function(num1,num2) {
return num1 - num2
})
console.log(arr1) //[2, 3, 4, 4, 6]
console.log(arr2) //[3, 3, 3, 4, 5, 6, 8]
console.log(arr1 === arr2) //false
console.log(arr1.sort === arr2.sort) //true
上述代码定义了两个数组并调用了sort方法来讲数组重新排序,在第三个输出中很明显两个数组是不相等的,但是它们调用的sort方法却是相等的,这是为什么呢?我们再看一个例子:
var arr1 = [2,4,6,4,3]
var arr2 = [3,5,3,6,4,8,3]
arr1.getSum = function() {
var sum = 0
for(var i = 0;i < this.length;i++) {
sum += this[i]
}
return sum
}
console.log(arr1.getSum()) //19
console.log(arr2.getSum()) //报错Uncaught TypeError: arr2.getSum is not a function
这个例子可以看出getSum函数只存在于arr1中,那为什么sort方法却能被两个数组同时使用呢?结合上面的例子可以猜测出一定有什么东西存放着所有数组都能共享的方法sort,那么就引出了原型(prototype)这个概念。通过原型的方法,我们能够做到让getSum函数也能被所有数组共享。
var arr1 = [2,4,6,4,3]
var arr2 = [3,5,3,6,4,8,3]
Array.prototype.getSum = function() {
var sum = 0
for(var i = 0;i < this.length;i++) {
sum += this[i]
}
return sum
}
console.log(arr1.getSum()) //19
console.log(arr2.getSum()) //32
console.log(arr1.getSum === arr2.getSum) //true
这个例子更能佐证了arr1和arr2是调用Array.prototype中的sort方法(当然Array.prototype中也还有很多其它的方法),如果不放心,大可打印出来看看:
console.log(arr1.sort === Array.prototype.sort) //true
- 添加给prototype的属性将会成为使用这个构造函数创建的对象的通用属性,方法同理
function Person(name,age) {
this.name = name
this.age = age
}
Person.prototype.profession = 'programmer'
var p1 = new Person('Joe',20)
var p2 = new Person('Bob',21)
var p3 = new Person('Mike',22)
console.log(p1.name,p1.age,p1.profession) //Joe 20 programmer
console.log(p2.name,p2.age,p2.profession) //Bob 21 programmer
console.log(p3.name,p3.age,p3.profession) //Mike 22 programmer
- 在外部不能通过prototype改变自定义类型的属性或方法
function Person() {
this.name = 'Joe'
this.say = function() {
console.log('hello')
}
}
Person.prototype.name = 'Bob'
Person.prototype.say = function() {
console.log('Hi!')
}
var p1 = new Person()
console.log(p1.name) //Joe
p1.say() //hello
- 函数中的prototype属性指向一个prototype对象(注意区别prototype属性与prototype对象是两个不同的东西)。在prototype对象中有一个constructor属性,这个constructor属性同样指向一个constructor对象,而这个constructor对象恰恰就是这个函数本身
function Person() {
this.name = name
}
console.log(Person.prototype.constructor === Person) //true
可以用图片这样子表示:
QQ图片20210305193109.png
- prototype 是一个指向该实例所使用的原型对象的指针
- prototype 是函数的属性,而不是对象的属性
function Person(name) {
this.name = name;
}
var p1 = new Person()
console.log(p1.prototype) //undefined
- prototype一般共享方法,不要共享数据
原型链
- 每个对象都会有原型,而该对象的原型指向原型对象(即父级对象),父级对象的原型又指向父级的父级,这种原型层层连接起来的就构成了原型链
- 实例对象的
__proto__
(隐式原型)指向它构造函数的prototype(显式原型)
function Person(name) { //内部语句:this.prototype = {}
this.name = name
}
var p1 = new Person() //内部语句:this.__proto__ = Person.prototype
console.log(p1.__proto__ === Person.prototype) //true
实例对象与其构造函数的关系可表示为:
QQ图片1.png
- 可以强行改变某实例对象的父级
function Person(name) {
this.name = name;
}
function Workder () {
this.sayHello = function() {
console.log('worker')
}
}
Person.prototype = new Workder()
var son = new Person()
console.log(son.__proto__) //Workder
- 前面说过prototype是一个指向该实例所使用的原型对象的指针,也就是说原型对象也是一个对象,那么它也可以由最原始创建对象的方法创建出来:
var p1 = new Object()
p1.name = 'Joe'
console.log(p1.name) // Joe
这个例子中,p1实例对象是由Object构造函数创建出来的,那么也符合p1.__proto__ === Object.prototype
,因为原型对象也是对象,我们假设p1本来就是某个对象的原型对象,那么Object.prototype也就是该原型对象__proto__
指向的原型对象,而所有对象都可以通过new Object()
来创建,所以原型链最终都会指向Object.prototype
。
function Person(name) {
this.name = name
}
var p1 = new Person()
console.log(p1.__proto__.__proto__ === Object.prototype) //true
该例再次说明了原型的原型确实会指向Object.prototype
,而原型链这条“链”便是一个个__proto__
连起来的。
那么链的终点是什么呢?
console.log(Object.prototype.__proto__) //null
可见,原型链的终点是null
- 实例访问属性或者方法的时候,遵循以为原则:如果实例上面存在,就用实例本身的属性和方法,否则,就会顺着
__proto__
的指向一直往上查找,查找就停止。
var parent = {
say() {
console.log('Hello')
}
}
function Person(name) {
this.name = name
}
Person.prototype = parent
var p1 = new Person()
p1.say() //Hello
//p1.sayName() //报错Uncaught TypeError: p1.sayName is not a function
console.log(p1.age) //undefined
var p2 = new Person()
p2.say = function() {
console.log('Hi')
}
p2.say() //Hi
总结
//构造函数
function Foo() {}
//实例对象
let f1 = new Foo()
let o1 = new Object()
console.log(f1.__proto__ === Foo.prototype) //true
console.log(Foo.prototype.constructor === Foo) //true
console.log(Foo.prototype.__proto__ === Object.prototype) //true 沿着原型链往上找都会找到Object.prototype
console.log(o1.__proto__ === Object.prototype) //true
console.log(Object.prototype.__proto__) //null 原型链的终点指向null
console.log(Foo.prototype.constructor === Foo) //true
console.log(Function.prototype.constructor === Function) //true
console.log(Object.__proto__ === Function.prototype) //true 内置的引用类型(包括Object)其实也是一个函数对象,都是由Function创建的,所以原型会指向Function.prototype
console.log(Foo.__proto__ === Function.prototype) //true
console.log(Function.prototype.prototype === undefined) //true Function.prototype是个特例,它是函数对象,但是没有prototype属性。其他所有函数都有prototype属性。
QQ图片.png
*由上图可知:
- 函数的显式原型指向的对象默认是空Object实例对象(但Object不满足)
console.log(Fn.prototype instanceof Object) //true
console.log(Object.prototype instanceof Object) //false
console.log(Function.prototype instanceof Object) //true
- 所有函数都是Function的实例(包括Function)
console.log(Function.__proto__ === Function.prototype) //true
注:插入的图片来自于网络。
网友评论