構(gòu)造函數(shù)和原型
- 能夠使用構(gòu)造函數(shù)創(chuàng)建對象
- 能夠說出原型的作用
- 能夠說出訪問對象成員的規(guī)則
- 能夠使用es5新增的一些方法
構(gòu)造函數(shù)(模擬類的實(shí)現(xiàn))
- 在典型的oop語言中(如java)刑峡,都存在類的概念,類就是對象的模板羽利,對象就是類的實(shí)例这弧,但是在ES6之前Js中并沒有引入類的概念
- 在ES6之前匾浪,對象不是基于類創(chuàng)建的蛋辈,而是用一種稱為構(gòu)造函數(shù)的特殊函數(shù)來定義對象和他們的特征
- 創(chuàng)建對象的三種方式
1.對象字面量
2.new Object()
3.構(gòu)造函數(shù)
// 1. 利用new Object() 創(chuàng)建對象
var obj1 = new Object();
// 2. 字面量創(chuàng)建對象
var obj2 = {};
// 3. 構(gòu)造函數(shù)創(chuàng)建對象
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('111');
}
}
var ldh = new Star('趙四', 18);
// 構(gòu)造函數(shù)要和new一起使用才會有意義
// 1. 實(shí)例成員
// 實(shí)例成員就是構(gòu)造函數(shù)內(nèi)部通過this添加的成員 uname age sing 就是實(shí)例成員
// 實(shí)例成員只能通過實(shí)例化的對象來訪問
// console.log(Star.uname);不可以通過這種構(gòu)造函數(shù)的方式來訪問實(shí)例成員
// 2. 靜態(tài)成員: 在構(gòu)造函數(shù)本身上添加的成員
Star.sex = '男'; // sex就是靜態(tài)成員
// 靜態(tài)成員只能通過構(gòu)造函數(shù)來訪問,不能通過對象來訪問逞频。
靜態(tài)方法和實(shí)例方法最主要的區(qū)別就是實(shí)例方法可以訪問到實(shí)例虏劲,可以對實(shí)例進(jìn)行操作柒巫,而靜態(tài)方法一般用于跟實(shí)例無關(guān)的操作堡掏。這兩種方法在 jQuery 中有大量應(yīng)用泉唁,在 jQuery 中$(selector)
其實(shí)拿到的就是實(shí)例對象,通過$(selector)
進(jìn)行操作的方法就是實(shí)例方法拴鸵。比如$(selector).append()
劲藐,這會往這個(gè)實(shí)例DOM添加新元素聘芜,他需要這個(gè) DOM 實(shí)例才知道怎么操作汰现,將append
作為一個(gè)實(shí)例方法服鹅,他里面的this
就會指向這個(gè)實(shí)例庐扫,就可以通過this
操作 DOM 實(shí)例形庭。
那什么方法適合作為靜態(tài)方法呢萨醒?比如$.ajax
富纸,這里的ajax
跟 DOM 實(shí)例沒關(guān)系晓褪,不需要這個(gè)this
涣仿,可以直接掛載在$上作為靜態(tài)方法好港。
- new 在執(zhí)行時(shí)會做四件事:
(1)在內(nèi)存中創(chuàng)建一個(gè)空對象钧汹;
(2)讓this指向這個(gè)空對象
(3)執(zhí)行構(gòu)造函數(shù)里面的代碼类嗤,給這個(gè)新對象添加屬性和方法
(4)返回這個(gè)新對象(所以構(gòu)造函數(shù)里面不需要return) - 構(gòu)造函數(shù)很好用,但是存在浪費(fèi)內(nèi)存的問題
- 我們希望所有的對象都是用同一個(gè)函數(shù)嗤形,這時(shí)候我們就用到了原型對象的概念
構(gòu)造函數(shù)原型prototype
- Js規(guī)定赋兵,每一個(gè)構(gòu)造函數(shù)里面都有prototype屬性,它指向一個(gè)對象历造,那么這個(gè)prototype就是一個(gè)對象吭产,我們稱之為原型對象臣淤,這個(gè)對象的所有屬性和方法就會被構(gòu)造函數(shù)所擁有邑蒋。
- 我們可以把一些不變的方法,直接定義在prototype對象上遮咖,這樣所有對象的實(shí)例就可以共享這些方法御吞。
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
var ldh = new Star('趙四', 18);
Star.prototype.sing = function() {
console.log('111');
}
console.log(ldh.sing());
// 一般情況下陶珠,我們的公共屬性定義到構(gòu)造函數(shù)里面诀蓉,公共的方法我們放到原型對象身上
- 原型的作用就是共享方法
- 但是這個(gè)時(shí)候大家可能有個(gè)疑惑渠啤,在ldh對象身上并沒有sing這個(gè)方法呀沥曹,是定義到了Star原型對象身上了,為什么ldh還能使用sing這個(gè)方法壶栋?
- 這是因?yàn)楣笫裕瑢ο蠖紩幸粋€(gè)屬性
__proto__
指向構(gòu)造函數(shù)的prototype原型對象锡移,之所以我們對象可以使用構(gòu)造函數(shù)prototype原型對象的屬性和方法,就是因?yàn)閷ο笥?code>__proto__原型的存在
// 驗(yàn)證一下
console.log(ldh.__proto__ === Star.prototype); // true
constructor 構(gòu)造函數(shù)
- 對象原型(
__proto__
)和構(gòu)造函數(shù)(prototype
)原型對象里面都有一個(gè)屬性constructor
屬性施符,constructor我們稱之為構(gòu)造函數(shù)戳吝,因?yàn)樗富貥?gòu)造函數(shù)本身。
// 我們打印一下可以驗(yàn)證
console.log(Star.prototype);
console.log(ldh.__proto__);
- 很多情況下我們需要手動的利用constructor這個(gè)屬性指回原來的構(gòu)造函數(shù)陆盘,例如:
// 我們把Star中的方法全部拿出來寫成統(tǒng)一的對象的方式
//Star.prototype.sing = function(){
// console.log('我會唱歌');
//}
//Star.prototype.movie= function(){
// console.log('我會演電影')酸员;
//}
// 如果利用下面這種寫法幔嗦,看上去也沒啥問題邀泉,只不過把方法以對象的方式寫入原型對象
// 但是這時(shí)候我們會發(fā)現(xiàn):console.log(Star.prototype.constructor); console.log(sing.__proto__.constructor);
// 他們打印的結(jié)果不再是原型對象本身,因?yàn)樾露x的下面的對象把Star本身帶的constructor對象覆蓋掉了屁置。
// 這時(shí)候我們就需要在下面的寫法中重新手動的利用constructor這個(gè)屬性指回原來的構(gòu)造函數(shù)
Star.prototype = {
// 這是上面這一大堆話的關(guān)鍵
constructor: Star,
sing: () => {
console.log('我會唱歌');
},
movie: () => {
console.log('我會演電影');
}
}
// 此時(shí)再打印
console.log(Star.prototype.constructor === ldh.__proto__.constructor); // true
// 如果沒有指回原來的構(gòu)造函數(shù),那么上面這個(gè)等式就不成立
- 對上面這個(gè)例子進(jìn)行總結(jié):如果我們修改了原來的原型對象使鹅,給原型對象賦值的是一個(gè)對象患朱,則必須手動的利用constructor指回原來的構(gòu)造函數(shù)炊苫。
構(gòu)造函數(shù)裁厅,原型,實(shí)例的關(guān)系
- 原型鏈:首先我們有一個(gè)構(gòu)造函數(shù)侨艾,每個(gè)構(gòu)造函數(shù)中都有一個(gè)
prototype
屬性(這個(gè)屬性是一個(gè)指針执虹,指向一個(gè)對象,這個(gè)對象包含了通過調(diào)用該構(gòu)造函數(shù)所創(chuàng)建的對象共享的屬性和方法唠梨。其實(shí)我們平常的叫法就是指:prototype就是通過該構(gòu)造函數(shù)創(chuàng)建的某個(gè)實(shí)例的原型對象),prototype
又指向這個(gè)原型對象当叭。同樣的原型對象里面有個(gè)屬性叫constructor
茬故,它又指回了構(gòu)造函數(shù)。而我們又知道蚁鳖,我們可以通過構(gòu)造函數(shù)來new一個(gè)實(shí)例對象均牢,在這個(gè)實(shí)例對象中也有一個(gè)原型叫__proto__
,這個(gè)__proto__
是指向prototype
這個(gè)原型對象的才睹。如圖:
原型鏈
- 只要是對象都會有一個(gè)
__proto__
原型 - 在prototype原型對象中也有一個(gè)
__proto__
原型,它指向的是Object.prototype - Object.prototype這個(gè)原型對象上面也有
__proto__
這個(gè)原型垮庐,它最終指向的是null空松邪。
原型對象this指向
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
var ldh = new Star('趙四', 18);
// 在構(gòu)造函數(shù)中,里面的this指向的是實(shí)例對象哨查,這里就是ldh逗抑;
var that = this;
Star.prototype.sing = function() {
console.log('111');
that = this;
}
ldh.sing();
console.log(ldh === that);// true
// 上面的例子說明,原型對象函數(shù)里面的this寒亥,指向的也是對象實(shí)例ldh邮府;
擴(kuò)展內(nèi)置對象
- 可以通過原型對象,對原來的內(nèi)置對象進(jìn)行擴(kuò)展自定義方法溉奕,比如給數(shù)組增加自定義求偶數(shù)和的功能褂傀。
// 我們先來打印一下數(shù)組的原型對象,發(fā)現(xiàn)原型對象上面沒有偶數(shù)和這個(gè)方法
console.log(Array.prototype);
Array.prototype.sum = function() {
for (let i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
}
var arr = [1, 2, 3];
console.log(arr.sum());
擴(kuò)展call方法
call()
加勤,兩個(gè)作用:
- 1.調(diào)用這個(gè)函數(shù)
- 2.修改函數(shù)運(yùn)行時(shí)的this指向
fun.call(thisArg, arg1, arg2, ...)
thisArg: 當(dāng)前調(diào)用函數(shù)this的指向?qū)ο?br> arg1仙辟,arg2: 傳遞的其他參數(shù)
function fn(x, y) {
console.log('111');
console.log(this); // 正常來說這里打印的this的指向應(yīng)該是window
console.log(x + y); // 3
}
var obj = {
name: 'li',
}
// 1. 調(diào)用函數(shù)
fn.call();
// 2. 改變函數(shù)內(nèi)部this的指向
fn.call(obj, 1, 2); // 通過call方法,我們可以實(shí)現(xiàn)把fn函數(shù)內(nèi)部的this指向obj這個(gè)對象鳄梅,這就是call的強(qiáng)大之處
繼承
- Es6之前是沒有類這個(gè)概念的叠国,也就沒有extends給我們提供繼承。我們可以通過構(gòu)造函數(shù)+原型對象模擬實(shí)現(xiàn)繼承戴尸,被稱為組合繼承粟焊。
- 核心原理:通過
call()
把父類型的this指向子類型的this,這樣就可以實(shí)現(xiàn)子類型繼承父類型的屬性孙蒙。
1. 借用父構(gòu)造函數(shù)繼承屬性
// 1. 父構(gòu)造函數(shù)
function Father(name, age) {
// this指向父構(gòu)造函數(shù)的對象實(shí)例
this.name = name;
this.age = age;
}
// 1. 子構(gòu)造函數(shù)
function Son(name, age) {
// this指向子構(gòu)造函數(shù)的對象實(shí)例
Father.call(this, name, age)
// 這里調(diào)用父構(gòu)造函數(shù)吆玖,利用call方法,可以把子構(gòu)造函數(shù)的this作為第一個(gè)參數(shù)马篮。
// 這樣就把父構(gòu)造函數(shù)的this指向了子構(gòu)造函數(shù)沾乘。
}
var son = new Son('劉德華', 18);
// 這時(shí)候在子構(gòu)造函數(shù)中就能夠使用父構(gòu)造函數(shù)中的屬性
2. 借用原型對象繼承方法
// 1. 父構(gòu)造函數(shù)
function Father(name, age) {
// this指向父構(gòu)造函數(shù)的對象實(shí)例
this.name = name;
this.age = age;
}
Father.prototype.money = function() {
console.log('我賺了這么多錢');
}
// 1. 子構(gòu)造函數(shù)
function Son(name, age) {
// this指向子構(gòu)造函數(shù)的對象實(shí)例
Father.call(this, name, age);
}
Son.prototype = new Father();
// 將Father的實(shí)例對象,賦值給Son構(gòu)造函數(shù)浑测,根據(jù)原型鏈的原理翅阵,F(xiàn)ather實(shí)例對象就可以獲取到money方法
// 那同樣的Son構(gòu)造函數(shù)也就可以使用money這個(gè)方法了
// 但是這里,我們將Son的構(gòu)造函數(shù)指向了Father的實(shí)例對象迁央,那么Son原來的constructor指向就有問題了掷匠,指向了Father。
console.log(Son.prototype.constructor);// --> Father
// 如果利用了對象的形式修改了原型對象岖圈,別忘了利用constructor指回原來的構(gòu)造函數(shù)
Son.prototype.constructor = Son;
var son = new Son('劉德華', 18);
console.log(son.money());
Es5新增方法
Es5給我們新增了一些方法讹语,可以很方便的操作數(shù)組或者字符串
1. 數(shù)組方法
- 迭代(遍歷)方法:forEach()、map()蜂科、filter()顽决、some()短条、every()
forEach()
// forEach方法
var Arr = [1, 2, 3];
Arr.forEach(function(value, index, array)) {
console.log('每個(gè)數(shù)組元素' + value);
console.log('每個(gè)數(shù)組元素索引號' + index);
console.log('數(shù)組元素本身' + array);
}
filter()
array.filter(function(currentValue, index, arr))
- filter方法創(chuàng)建一個(gè)新數(shù)組,新數(shù)組中的元素是通過檢查指定數(shù)組中符合條件的所有元素才菠,主要用于篩選數(shù)組茸时。
- 注意它直接返回一個(gè)新數(shù)組
- currentValue :數(shù)組當(dāng)前項(xiàng)的值
- index:數(shù)組當(dāng)前項(xiàng)的索引值
- arr:數(shù)組對象本身
// filter方法
var Arr = [12, 66, 4, 88];
// 選取大于20的選項(xiàng)
var newArray = Arr.filter(function(value, index) {
return value >= 20;
});
console.log(newArray);
some()
array.some(function(currentValue, index, arr))
- some()方法用于檢測數(shù)組中的元素是否滿足指定條件,通俗點(diǎn)查找數(shù)組中是否有滿足條件的元素
- 注意它返回值是布爾值赋访,如果查找到這個(gè)元素就返回true可都,如果找不到就返回false
- 如果找到第一個(gè)滿足條件的元素,則終止循環(huán)蚓耽,不再繼續(xù)查找
- currentValue :數(shù)組當(dāng)前項(xiàng)的值
- index:數(shù)組當(dāng)前項(xiàng)的索引值
- arr:數(shù)組對象本身
// some方法
var Arr = [10, 30, 4];
var flag = Arr.some(function(value) {
// 尋找是否存在大于20的元素
return value >= 20;
})
console.log(flag);// true
map()
array.map(function(currentValue, index, arr))
- map() 方法返回一個(gè)新數(shù)組渠牲,數(shù)組中的元素為原始數(shù)組元素調(diào)用函數(shù)處理后的值。
- map() 方法按照原始數(shù)組元素順序依次處理元素步悠。和forEach有點(diǎn)相似签杈。
- 注意: map() 不會對空數(shù)組進(jìn)行檢測。
- 注意: map() 不會改變原始數(shù)組贤徒。
// 數(shù)組中的每個(gè)元素乘于輸入框指定的值,并返回新數(shù)組:
var numbers = [65, 44, 12, 4];
function multiplyArrayElement(num) {
return num * document.getElementById("multiplyWith").value;
}
function myFunction() {
document.getElementById("demo").innerHTML = numbers.map(multiplyArrayElement);
}
every()
array.every(function(currentValue, index, arr))
- every() 方法用于檢測數(shù)組所有元素是否都符合指定條件(通過函數(shù)提供)汇四。和some類似
- every() 方法使用指定函數(shù)檢測數(shù)組中的所有元素:
- 如果數(shù)組中檢測到有一個(gè)元素不滿足接奈,則整個(gè)表達(dá)式返回 false ,且剩余的元素不會再進(jìn)行檢測通孽。
如果所有元素都滿足條件序宦,則返回 true。 - 注意: every() 不會對空數(shù)組進(jìn)行檢測背苦。
- 注意: every() 不會改變原始數(shù)組互捌。
// 檢測數(shù)組 ages 的所有元素是否都大于等于 18 :
var ages = [32, 33, 16, 40];
function checkAdult(age) {
return age >= 18;
}
function myFunction() {
document.getElementById("demo").innerHTML = ages.every(checkAdult);
}
2. 字符串方法
trim()
str.trim()
- trim()方法會從一個(gè)字符串的兩端刪除空白字符
- trim方法并不影響原來的字符串本身,它會返回一個(gè)新的字符串行剂。
3. 對象方法
Object.keys()
- Object.keys()用于獲取對象自身所有的屬性秕噪。
Object.keys(obj)
- 效果類似for...in
- 返回一個(gè)由屬性名構(gòu)成的數(shù)組
var obj = {
name: '張三',
age: 18,
sex: '男'
}
Object.keys(obj);
console.log(Object.keys(obj));
// ["name", "age", "sex"]
Object.defineProperty()
Object.defineProperty(obj, prop, descriptor)
- Object.defineProperty()作用是定義新屬性,或者是修改原有的屬性厚宰。
- Object.defineProperty()第三個(gè)參數(shù)descriptor說明:以對象的形式書寫{}
1.value:設(shè)置屬性的值腌巾,默認(rèn)為undefined
2.writable:值是否可以重寫。默認(rèn)為false
3.enumerable:目標(biāo)屬性是否可以被枚舉铲觉。默認(rèn)為false
4.configrable:目標(biāo)屬性是否可以被刪除或者是否可以再次修改特性澈蝙。默認(rèn)為false
var obj = {
name: '張三',
age: 18,
sex: '男'
}
//obj.num = 1000;// 以前我們用這種方式增加或修改對象屬性
Object.defineProperty(obj, 'age', {
value: 19,
});
Object.defineProperty(obj, 'age', {
// 不允許修改這個(gè)屬性值
writable: false,
});
Object.defineProperty(obj, 'address', {
value: '中國山東',
// 如果值為false,則不允許遍歷此項(xiàng)撵幽,默認(rèn)為false
enumerable: false,
// 如果值為false灯荧,則不允許刪除這個(gè)屬性,而且接下來也不允許修改第三個(gè)參數(shù)里面的特性盐杂,默認(rèn)false逗载,
configurable: false,
})
delete obj.address;// 試圖刪除address屬性
ES6:Class 的繼承
Class 可以通過extends關(guān)鍵字實(shí)現(xiàn)繼承哆窿,這比 ES5 的通過修改原型鏈實(shí)現(xiàn)繼承,要清晰和方便很多撕贞。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 調(diào)用父類的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 調(diào)用父類的toString()
}
}
let cp = new ColorPoint(25, 8, 'green');
cp instanceof ColorPoint // true
cp instanceof Point // true
上面代碼中更耻,constructor方法和toString方法之中,都出現(xiàn)了super關(guān)鍵字捏膨,它在這里表示父類的構(gòu)造函數(shù)秧均,用來新建父類的this對象。
子類必須在constructor方法中調(diào)用super方法号涯,否則新建實(shí)例時(shí)會報(bào)錯(cuò)目胡。這是因?yàn)樽宇愖约旱膖his對象,必須先通過父類的構(gòu)造函數(shù)完成塑造链快,得到與父類同樣的實(shí)例屬性和方法誉己,然后再對其進(jìn)行加工,加上子類自己的實(shí)例屬性和方法域蜗。如果不調(diào)用super方法巨双,子類就得不到this對象。
實(shí)例對象cp同時(shí)是ColorPoint和Point兩個(gè)類的實(shí)例霉祸,這與 ES5 的行為完全一致筑累。
最后,父類的靜態(tài)方法丝蹭,也會被子類繼承慢宗。
Object.getPrototypeOf()
Object.getPrototypeOf方法可以用來從子類上獲取父類。
Object.getPrototypeOf(ColorPoint) === Point
// true
因此奔穿,可以使用這個(gè)方法判斷镜沽,一個(gè)類是否繼承了另一個(gè)類。
super關(guān)鍵字
super
這個(gè)關(guān)鍵字贱田,既可以當(dāng)作函數(shù)使用缅茉,也可以當(dāng)作對象使用。在這兩種情況下男摧,它的用法完全不同宾舅。
第一種情況,super
作為函數(shù)調(diào)用時(shí)彩倚,代表父類的構(gòu)造函數(shù)筹我。ES6 要求,子類的構(gòu)造函數(shù)必須執(zhí)行一次super
函數(shù)帆离。
class A {}
class B extends A {
constructor() {
super();
}
}
上面代碼中蔬蕊,子類B
的構(gòu)造函數(shù)之中的super()
,代表調(diào)用父類的構(gòu)造函數(shù)。這是必須的岸夯,否則 JavaScript 引擎會報(bào)錯(cuò)麻献。
注意,super
雖然代表了父類A
的構(gòu)造函數(shù)猜扮,但是返回的是子類B
的實(shí)例勉吻,即super
內(nèi)部的this
指的是B
的實(shí)例,因此super()
在這里相當(dāng)于A.prototype.constructor.call(this)
旅赢。
class A {
constructor() {
console.log(new.target.name);
}
}
class B extends A {
constructor() {
super();
}
}
new A() // A
new B() // B
上面代碼中齿桃,new.target
指向當(dāng)前正在執(zhí)行的函數(shù)≈笈危可以看到短纵,在super()
執(zhí)行時(shí),它指向的是子類B
的構(gòu)造函數(shù)僵控,而不是父類A
的構(gòu)造函數(shù)香到。也就是說,super()
內(nèi)部的this
指向的是B
报破。
作為函數(shù)時(shí)悠就,super()
只能用在子類的構(gòu)造函數(shù)之中,用在其他地方就會報(bào)錯(cuò)充易。
class A {}
class B extends A {
m() {
super(); // 報(bào)錯(cuò)
}
}
上面代碼中梗脾,super()
用在B
類的m
方法之中,就會造成語法錯(cuò)誤蔽氨。
第二種情況藐唠,super
作為對象時(shí)帆疟,在普通方法中鹉究,指向父類的原型對象;在靜態(tài)方法中踪宠,指向父類自赔。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
上面代碼中,子類B
當(dāng)中的super.p()
柳琢,就是將super
當(dāng)作一個(gè)對象使用绍妨。這時(shí),super
在普通方法之中柬脸,指向A.prototype
他去,所以super.p()
就相當(dāng)于A.prototype.p()
。
這里需要注意倒堕,由于super
指向父類的原型對象灾测,所以定義在父類實(shí)例上的方法或?qū)傩裕菬o法通過super
調(diào)用的垦巴。
class A {
constructor() {
this.p = 2;
}
}
class B extends A {
get m() {
return super.p;
}
}
let b = new B();
b.m // undefined
上面代碼中媳搪,p
是父類A
實(shí)例的屬性铭段,super.p
就引用不到它。
如果屬性定義在父類的原型對象上秦爆,super
就可以取到序愚。
class A {}
A.prototype.x = 2;
class B extends A {
constructor() {
super();
console.log(super.x) // 2
}
}
let b = new B();
上面代碼中,屬性x
是定義在A.prototype
上面的等限,所以super.x
可以取到它的值爸吮。
ES6 規(guī)定,在子類普通方法中通過super
調(diào)用父類的方法時(shí)精刷,方法內(nèi)部的this
指向當(dāng)前的子類實(shí)例拗胜。
class A {
constructor() {
this.x = 1;
}
print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
m() {
super.print();
}
}
let b = new B();
b.m() // 2
上面代碼中,super.print()
雖然調(diào)用的是A.prototype.print()
怒允,但是A.prototype.print()
內(nèi)部的this
指向子類B
的實(shí)例埂软,導(dǎo)致輸出的是2
,而不是1
纫事。也就是說勘畔,實(shí)際上執(zhí)行的是super.print.call(this)
。
由于this
指向子類實(shí)例丽惶,所以如果通過super
對某個(gè)屬性賦值炫七,這時(shí)super
就是this
,賦值的屬性會變成子類實(shí)例的屬性钾唬。
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3;
console.log(super.x); // undefined
console.log(this.x); // 3
}
}
let b = new B();
上面代碼中万哪,super.x
賦值為3
,這時(shí)等同于對this.x
賦值為3
抡秆。而當(dāng)讀取super.x
的時(shí)候奕巍,讀的是A.prototype.x
,所以返回undefined
儒士。
如果super
作為對象的止,用在靜態(tài)方法之中,這時(shí)super
將指向父類着撩,而不是父類的原型對象诅福。
class Parent {
static myMethod(msg) {
console.log('static', msg);
}
myMethod(msg) {
console.log('instance', msg);
}
}
class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg);
}
myMethod(msg) {
super.myMethod(msg);
}
}
Child.myMethod(1); // static 1
var child = new Child();
child.myMethod(2); // instance 2
上面代碼中,super
在靜態(tài)方法之中指向父類拖叙,在普通方法之中指向父類的原型對象氓润。
另外,在子類的靜態(tài)方法中通過super
調(diào)用父類的方法時(shí)薯鳍,方法內(nèi)部的this
指向當(dāng)前的子類咖气,而不是子類的實(shí)例。
class A {
constructor() {
this.x = 1;
}
static print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
static m() {
super.print();
}
}
B.x = 3;
B.m() // 3
上面代碼中,靜態(tài)方法B.m
里面采章,super.print
指向父類的靜態(tài)方法运嗜。這個(gè)方法里面的this
指向的是B
,而不是B
的實(shí)例悯舟。
注意担租,使用super
的時(shí)候,必須顯式指定是作為函數(shù)抵怎、還是作為對象使用奋救,否則會報(bào)錯(cuò)。
class A {}
class B extends A {
constructor() {
super();
console.log(super); // 報(bào)錯(cuò)
}
}
上面代碼中反惕,console.log(super)
當(dāng)中的super
尝艘,無法看出是作為函數(shù)使用,還是作為對象使用姿染,所以 JavaScript 引擎解析代碼的時(shí)候就會報(bào)錯(cuò)背亥。這時(shí),如果能清晰地表明super
的數(shù)據(jù)類型悬赏,就不會報(bào)錯(cuò)狡汉。
class A {}
class B extends A {
constructor() {
super();
console.log(super.valueOf() instanceof B); // true
}
}
let b = new B();
上面代碼中,super.valueOf()
表明super
是一個(gè)對象闽颇,因此就不會報(bào)錯(cuò)盾戴。同時(shí),由于super
使得this
指向B
的實(shí)例兵多,所以super.valueOf()
返回的是一個(gè)B
的實(shí)例尖啡。
最后,由于對象總是繼承其他對象的剩膘,所以可以在任意一個(gè)對象中衅斩,使用super
關(guān)鍵字。
var obj = {
toString() {
return "MyObject: " + super.toString();
}
};
obj.toString(); // MyObject: [object Object]
總結(jié)
JS中的函數(shù)可以作為函數(shù)使用援雇,也可以作為類使用
作為類使用的函數(shù)實(shí)例化時(shí)需要使用new
為了讓函數(shù)具有類的功能矛渴,函數(shù)都具有
prototype
屬性椎扬。為了讓實(shí)例化出來的對象能夠訪問到
prototype
上的屬性和方法惫搏,實(shí)例對象的__proto__
指向了類的prototype
。所以prototype
是函數(shù)的屬性蚕涤,不是對象的筐赔。對象擁有的是__proto__
,是用來查找prototype
的揖铜。prototype.constructor
指向的是構(gòu)造函數(shù)茴丰,也就是類函數(shù)本身。改變這個(gè)指針并不能改變構(gòu)造函數(shù)。對象本身并沒有
constructor
屬性贿肩,你訪問到的是原型鏈上的prototype.constructor
峦椰。函數(shù)本身也是對象,也具有
__proto__
汰规,他指向的是JS內(nèi)置對象Function
的原型Function.prototype
汤功。所以你才能調(diào)用func.call
,func.apply
這些方法,你調(diào)用的其實(shí)是Function.prototype.call
和Function.prototype.apply
溜哮。prototype
本身也是對象滔金,所以他也有__proto__
,指向了他父級的prototype
茂嗓。__proto__
和prototype
的這種鏈?zhǔn)街赶驑?gòu)成了JS的原型鏈餐茵。原型鏈的最終指向是Object
的原型。Object
上面原型鏈?zhǔn)莕ull述吸,即Object.prototype.__proto__ === null
忿族。有朋友提到:
Function.__proto__ === Function.prototype。
這是因?yàn)镴S中所有函數(shù)的原型都是Function.prototype
蝌矛,也就是說所有函數(shù)都是Function
的實(shí)例肠阱。Function
本身也是可以作為函數(shù)使用的----Function()
,所以他也是Function
的一個(gè)實(shí)例朴读。類似的還有Object
屹徘,Array
等,他們也可以作為函數(shù)使用:Object()
,Array()
衅金。所以他們本身的原型也是Function.prototype
噪伊,即Object.__proto__ === Function.prototype
。換句話說氮唯,這些可以new的內(nèi)置對象其實(shí)都是一個(gè)類鉴吹,就像我們的Puppy類一樣。ES6的class其實(shí)是函數(shù)類的一種語法糖惩琉,書寫起來更清晰豆励,但原理是一樣的。