JavaScript原型与原型链

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

注:插入的图片来自于网络。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容