1.構(gòu)造函數(shù)和原型?
1.1 概述
在典型的?OOP語言中(如Java),都存在類的概念,類就是對(duì)象的模板,對(duì)象就是類的實(shí)例,但在ES6之前,JS并沒有引入類的概念。
在ES6之前,對(duì)象不是基于類創(chuàng)建的,而是一種稱為構(gòu)建函數(shù)的特殊函數(shù)來定義對(duì)象和它們的特征。
有三種創(chuàng)建對(duì)象的方式:
-
對(duì)象字面量(const obj = {name:'ab'})
-
new Object()
-
自定義構(gòu)造函數(shù)
//構(gòu)造函數(shù)
function Star(uname,age){
?
this.uname = uname;
this.age = age
this.sing = function(){
console.log("我會(huì)唱歌")
}
?
}
const ldh = new Star("劉德華",18)
const syz = new Star("孫燕姿",17)
console.log(ldh)
ldh.sing()
syz.sing()
?
1.2 構(gòu)造函數(shù)
構(gòu)造函數(shù)是一種特殊的函數(shù),主要用來初始化對(duì)象,即為對(duì)象成員變量賦初始值,它總與new一起使用。我們可以把對(duì)象中一些公共的屬性和方法抽取出來,然后封裝到這個(gè)函數(shù)里面。
?
在JS中使用構(gòu)造函數(shù)是要注意以下兩點(diǎn):
-
構(gòu)造函數(shù)用于創(chuàng)建某一類對(duì)象,其首字母要大寫
-
構(gòu)造函數(shù)要和new一起使用才有意義
?
new在執(zhí)行時(shí)會(huì)做四件事情:
-
在內(nèi)存中創(chuàng)建一個(gè)新的空對(duì)象
-
讓this指向這個(gè)新的對(duì)象
-
執(zhí)行構(gòu)造函數(shù)里面的代碼,給這個(gè)新對(duì)象添加屬性和方法
-
返回這個(gè)新對(duì)象(所有構(gòu)造函數(shù)里面不需要renturn)
?
1.3 實(shí)例成員和靜態(tài)成員
實(shí)例成員:
構(gòu)造函數(shù)內(nèi)部通過this添加的成員 ,如下圖的uname,age,sing就是實(shí)例成員
實(shí)例成員只能通過實(shí)例化的對(duì)象來訪問
function Star(uname,age){
?
this.uname = unamw;
?
this.age = age
?
this.sing = function(){
?
console.log("我會(huì)唱歌")
?
}
}
const ldh = new Star("劉德華",18)
靜態(tài)成員:
靜態(tài)成員在構(gòu)造函數(shù)本身上添加的成員,如下圖的sex就是靜態(tài)成員
鏡頭成員只能通過構(gòu)造函數(shù)來訪問不能通過對(duì)象訪問。
Star.sex = '男'
?
console.log('Star.sex')
?
1.4構(gòu)造函數(shù)的問題
構(gòu)造函數(shù)方法很好用,但存在浪費(fèi)內(nèi)存的問題:
如:Star()構(gòu)造函數(shù)中的sing()方法在每次實(shí)例化對(duì)象的時(shí)候都需要單獨(dú)開辟一份內(nèi)存空間,存在浪費(fèi)空間的問題。
但是我們希望所有的對(duì)象使用同一個(gè)函數(shù),這樣就比較節(jié)省內(nèi)存。
//構(gòu)造函數(shù)
function Star(uname, age) {
this.uname = uname;
?
this.age = age;
?
this.sing = function () {
console.log("我會(huì)唱歌");
};
}
const ldh = new Star("劉德華", 18);
const syz = new Star("孫燕姿", 17);
?
思考:可是,為什么每次實(shí)例化都是單獨(dú)開辟空間呢?
這個(gè)問題我查閱了很多的資料,總結(jié)一句話就是:在JS中,引用類型被創(chuàng)建的時(shí)候都會(huì)開辟一個(gè)新的空間。(其中的知識(shí)點(diǎn)比較多,詳情請期待下一篇文章~)
?
1.5 構(gòu)造函數(shù)原型prototype
構(gòu)造函數(shù)通過原型分配的函數(shù)是所有對(duì)象所共享的。
JavaScript規(guī)定,每一個(gè)構(gòu)造函數(shù)都有一個(gè)prototype?屬性,指向另一個(gè)對(duì)象。
注意:?這個(gè)prototype就是一個(gè)對(duì)象,這個(gè)對(duì)象所有的屬性和方法都會(huì)被構(gòu)造函數(shù)所擁有。
我們可以把那些不變的方法,直接定義在prototype 對(duì)象上,這樣所有對(duì)象的實(shí)例就可以共享這些方法。
function Star(uname,age){
?
this.uname = unamw;
this.age = age
}
Star.sing = function(){
console.log("我會(huì)唱歌")
}
?
const syz = new Star('孫燕姿',20)
?
syz.sing()//我會(huì)唱歌
? ?
-
原型是什么??-------是一個(gè)對(duì)象,我們也稱prototype為原型對(duì)象
-
原型的作用是什么??------共享方法
?
1.6對(duì)象原型 __ proto __
對(duì)象都會(huì)有一個(gè)屬性 __ prpto __?指向構(gòu)造函數(shù)的?prototype 原型對(duì)象,之所以對(duì)象可以使用構(gòu)造函數(shù) prototype 原型對(duì)象的屬性和方法,就是因?yàn)閷?duì)象有__ proto __ 原型的存在。
總結(jié):
-
__ proto __ 對(duì)象原型和原型對(duì)象 prototype 是等價(jià)的
function Star(uname,age){
?
this.uname = unamw;
this.age = age
}
Star.sing = function(){
console.log("我會(huì)唱歌")
}
?
const syz = new Star('孫燕姿',20)
console.log(syz.__ptoto === Star.prototype)//等價(jià)
?
-
__ proto __ 對(duì)象原型的意義就在于為對(duì)象的查找機(jī)制提供一個(gè)方向或者一條線路,但是它是一個(gè)非標(biāo)準(zhǔn)屬性,因此實(shí)際開發(fā)中,不可以使用這個(gè)屬性,它只是內(nèi)部指向原型對(duì)象prototype。
?
1.7 constructor構(gòu)造函數(shù)
對(duì)象原型(__ proto __)和構(gòu)造函數(shù)原型對(duì)象(prototype)里面都有一個(gè)屬性:constructor屬性,constructor 我們稱為構(gòu)造函數(shù),因?yàn)樗?strong>指回構(gòu)造函數(shù)本身。
function Star(uname,age){
?
this.uname = unamw;
this.age = age
}
Star.prototype = {
//這種情況下我們已經(jīng)修改了原來prototype,給原型對(duì)象賦值的是一個(gè)對(duì)象,所有必須手動(dòng)的把 constructor指回原來的構(gòu)造函數(shù)
constructor:Star,
sing:function(){
console.log("唱歌")
}
movie:function(){
console.log("電影")
}
}
?
?
1.8構(gòu)造函數(shù)實(shí)例原型對(duì)象三者之間的關(guān)系
?
?
總結(jié):構(gòu)造函數(shù)和原型對(duì)象之間有互相可以表明對(duì)方身份的”信物“:prototype 和 constructor。
1.9 原型鏈
?
?
總結(jié):
ldh對(duì)象實(shí)例的原型(__ proto __)可以找到它的對(duì)象原型——Star原型對(duì)象prototype;
通過Star原型對(duì)象prototype的原型(__ proto __),可以找到它的對(duì)象原型——Object原型對(duì)象 prototype;
通過Object原型對(duì)象 prototype的原型(__ proto __),可以找到它的對(duì)象原型——null(最頂層)。
因此形成的線路叫做原型鏈,為我們提供了某個(gè)屬性或者函數(shù)的查找的線路。
1.10 原型鏈查找規(guī)則
-
當(dāng)訪問一個(gè)對(duì)象的屬性(包括方法)時(shí),首先查找這個(gè)對(duì)象自身有沒有該屬性
-
如果沒有就查找它的原型(也就是__ proto __ 指向的prototype原型對(duì)象)
-
如果還沒有就查找原型對(duì)象的原型(Object的原型對(duì)象)
-
以此類推一直找到最頂層(null)
1.11原型對(duì)象的this指向
function Star(uname,age){
?
this.uname = unamw;
this.age = age
}
?
let that;
Star.prototype.sing = function(){
that = this
console.log("我會(huì)唱歌")
}
const ldh = new Star("劉德華",18)
ldh.sing()
console.log(that === ldh)//true
?
在構(gòu)造函數(shù)(star)中?this?指向 創(chuàng)建的實(shí)例對(duì)象(ldh)。
sing函數(shù)只有調(diào)用之后才能確然this的指向,一般原則是:誰調(diào)用,this指向誰。
?
1.12 原型對(duì)象的應(yīng)用
擴(kuò)展內(nèi)置對(duì)象
可以通過原型對(duì)象對(duì)原來的內(nèi)置對(duì)象進(jìn)行擴(kuò)展自定義的方法。比如:給數(shù)組增加自定義求和的功能
Array.prototype.sum = function(){
let sum = 0
for(let i = 0;i < this.length; i++){
sum += this[i]
}
return sum;
}
let arr1 = [1,4,5,6,8,9]
?
console.log(arr1.sum())//33
cosole.log(Array.prototype)//可以在arry中看到擴(kuò)展的求和方法
?
2.繼承
ES6 之前并沒有給我們提供extends繼承。我們可以通過構(gòu)造函數(shù)+原型對(duì)象模擬實(shí)現(xiàn)繼承,被稱為組合繼承。
2.1call()
調(diào)用這個(gè)函數(shù),并且修改函數(shù)運(yùn)行時(shí)的this指向。
fun.call(thisArg, arg1, arg2... )
-
thisArg:當(dāng)前調(diào)用函數(shù)this的指向?qū)ο?/span>
-
arg1 ,arg2: 傳遞的其他對(duì)象
const o = {
name: 'zooey'
}
function fn(x,y){
console.log("輸出這個(gè)函數(shù)")
console.log(this) // 此時(shí) this 是 window
console.log(x+y)
}
//普通調(diào)用的方式
fn()
?
//call調(diào)用方式: 使用對(duì)象調(diào)用, 此時(shí)的 this 是 o
fn.call(o) //此時(shí) this 輸出是 O
?
//call 做一些其他操作 比如:求出后兩個(gè)參數(shù)的和
fn.call(o,5,2) //此時(shí) this 輸出是 O
2.2借用構(gòu)造函數(shù)繼承父類屬性
核心原理:通過call 把父類的this 指向子類型的this ,這樣就可以實(shí)現(xiàn)子類型繼承夫類型的屬性。
function Father(uname, age) {
this.uname = uname;
this.age = age;
}
?
function Son(uname, age, score) {
/**
使用call調(diào)用Father 構(gòu)造方法,
并且把Father構(gòu)造方法的this ,修改為Son的調(diào)用者
傳遞參數(shù)uname,age,將參數(shù)也綁定給Father
*/
Father.call(this, uname, age);
this.score = score;
}
?
const son = new Son("lan", 26, 100);
console.log(son);
?
注意點(diǎn):
在 Son 構(gòu)造函數(shù)中第一個(gè)參數(shù)是將 Son 的this傳遞給Father ,但是Son只有在實(shí)例化時(shí)才能知道this是誰。
?
初始化的參數(shù)通過Son傳遞給Father
?
?
2.3借用原型對(duì)象繼承父類型方法
此處有一個(gè)思考:
在下圖中,Array的原型對(duì)象中增加求和方法sum,在實(shí)例對(duì)arr1中就可以直接使用,但是在非實(shí)例化的形式中,如何實(shí)現(xiàn)方法的繼承呢?
?
案例
function Father(uname, age) {
this.uname = uname;
this.age = age;
}
?
Father.prototype.getMoney = function () {
console.log("掙錢");
};
?
function Son(uname, age, score) {
/**
使用call調(diào)用Father 構(gòu)造方法,
并且把Father構(gòu)造方法的this ,修改為Son的調(diào)用者
傳遞參數(shù)uname,age,將參數(shù)也綁定給Father
*/
Father.call(this, uname, age);
this.score = score;
}
Son.prototype.exam = function () {
console.log("孩子考試");
};
?
const son = new Son("lan", 26, 100);
console.log(son);
son.exam();
son.getMoney();
?
?
| 可以看到上圖中,son實(shí)例對(duì)象不能調(diào)用getMoney()方法
| 也不能將Father的prototype直接賦值給Son ,如下圖
//直接賦值的操作
Son.prototype = Father.prototype;
Son.prototype.exam = function () {
console.log("孩子考試");
};
//此時(shí)對(duì)Son的prototype修改也會(huì)是 Father擁有 exam方法,因?yàn)楝F(xiàn)在是: Son的prototype 指向了Father 的 prototype,兩個(gè)構(gòu)造函數(shù)本質(zhì)上是一個(gè)prototype.
console.log(Father);
?
注意: 此時(shí)對(duì)Son的prototype修改也會(huì)是 Father擁有 exam方法,因?yàn)楝F(xiàn)在是: Son的prototype 指向了Father 的 prototype,兩個(gè)構(gòu)造函數(shù)本質(zhì)上是一個(gè)prototype.
正確的做法:
Son.prototype = new Father();
//記得將constructor指回 Son構(gòu)造函數(shù)
Son.prototype.constructor = Son;
?
Son.prototype.exam = function () {
console.log("孩子考試");
};
console.log(Father);
思考:記得將constructor指回 Son構(gòu)造函數(shù) 的原因是什么呢?
嘗試注釋掉指回構(gòu)造函數(shù)的代碼,也可以正常輸出,只不過沒有constructor參數(shù)
文章來源:http://www.zghlxwxcb.cn/news/detail-709735.html
只是son實(shí)例可以歸溯自己的構(gòu)造函數(shù)是誰.文章來源地址http://www.zghlxwxcb.cn/news/detail-709735.html
到了這里,關(guān)于JS深入學(xué)習(xí)筆記 - 第一章.構(gòu)造函數(shù)原型與原型鏈的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!