[20190507]
- 實(shí)例對(duì)象與 new 命令
- this 關(guān)鍵字
- 對(duì)象的繼承
- Object 對(duì)象的相關(guān)方法
- 嚴(yán)格模式
面向?qū)ο缶幊蹋∣bject Oriented Programming,縮寫(xiě)為 OOP)是目前主流的編程范式。它將真實(shí)世界各種復(fù)雜的關(guān)系,抽象為一個(gè)個(gè)對(duì)象中剩,然后由對(duì)象之間的分工與合作箍邮,完成對(duì)真實(shí)世界的模擬上渴。
典型的面向?qū)ο缶幊陶Z(yǔ)言(比如 C++ 和 Java)瞒斩,都有“類”(class)這個(gè)概念。所謂“類”就是對(duì)象的模板劳闹,對(duì)象就是“類”的實(shí)例。但是洽瞬,JavaScript 語(yǔ)言的對(duì)象體系本涕,不是基于“類”的,而是基于構(gòu)造函數(shù)(constructor)和原型鏈(prototype)伙窃。
JavaScript 語(yǔ)言使用構(gòu)造函數(shù)(constructor)作為對(duì)象的模板菩颖。
構(gòu)造函數(shù)的名字第一個(gè)字母大寫(xiě)。
// 構(gòu)造函數(shù)
var Vehicle = function() {
this.price = 1000;
}
var v = new Vehicle();
v.price // 1000
構(gòu)造函數(shù)的特點(diǎn)有兩個(gè)对供。
- 函數(shù)體內(nèi)部使用了this關(guān)鍵字位他,代表了所要生成的對(duì)象實(shí)例氛濒。
- 生成對(duì)象的時(shí)候,必須使用new命令鹅髓。
使用new命令時(shí)舞竿,根據(jù)需要,構(gòu)造函數(shù)也可以接受參數(shù)窿冯。
var Vehicle = function(p) {
this.price = p;
}
var v = new Vehicle(500);
不用new的話骗奖,直接調(diào)用構(gòu)造函數(shù)的話,等同于直接調(diào)普通函數(shù)醒串。
一個(gè)很自然的問(wèn)題是执桌,如果忘了使用new命令,直接調(diào)用構(gòu)造函數(shù)會(huì)發(fā)生什么事芜赌?
這種情況下仰挣,構(gòu)造函數(shù)就變成了普通函數(shù),并不會(huì)生成實(shí)例對(duì)象缠沈。而且由于后面會(huì)說(shuō)到的原因膘壶,this這時(shí)代表全局對(duì)象,將造成一些意想不到的結(jié)果洲愤。
var Vehicle = function (){
this.price = 1000;
};
var v = Vehicle();
v // undefined
price // 1000
嚴(yán)格模式下颓芭,可以顯示提醒這是個(gè)錯(cuò)誤用法。
function Fubar(foo, bar){
'use strict';
this._foo = foo;
this._bar = bar;
}
Fubar()
// TypeError: Cannot set property '_foo' of undefined
嚴(yán)格模式下柬赐,函數(shù)內(nèi)部的this不能指向全局對(duì)象亡问。
更健壯的寫(xiě)法是:
function Fubar(foo, bar) {
if !(this instanceof Fubar) {
return new Fubar(foo, bar);
}
this._foo = foo;
this._bar = bar;
}
Fubar(1, 2)._foo // 1
(new Fubar(1, 2))._foo // 1
執(zhí)行new的過(guò)程:
- 創(chuàng)建一個(gè)空對(duì)象,作為將要返回的對(duì)象實(shí)例肛宋。
- 將這個(gè)空對(duì)象的原型州藕,指向構(gòu)造函數(shù)的prototype屬性。
- 將這個(gè)空對(duì)象賦值給函數(shù)內(nèi)部的this關(guān)鍵字酝陈。
- 開(kāi)始執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼慎框。
this
指向新生成的空對(duì)象,針對(duì)this
的操作后添,都會(huì)發(fā)生在這個(gè)空對(duì)象上笨枯。
構(gòu)造函數(shù)之所以叫“構(gòu)造函數(shù)”,就是說(shuō)這個(gè)函數(shù)的目的遇西,就是操作一個(gè)空對(duì)象(即this對(duì)象)馅精,將其“構(gòu)造”為需要的樣子。
如果構(gòu)造函數(shù)內(nèi)部有return語(yǔ)句粱檀,而且return后面跟著一個(gè)對(duì)象洲敢,new命令會(huì)返回return語(yǔ)句指定的對(duì)象;否則茄蚯,就會(huì)不管return
語(yǔ)句压彭,返回this
對(duì)象睦优。
var Vehicle = function () {
this.price = 1000;
return 1000;
};
(new Vehicle()) === 1000
// false
上面代碼中,構(gòu)造函數(shù)Vehicle的return語(yǔ)句返回一個(gè)數(shù)值壮不。這時(shí)汗盘,new命令就會(huì)忽略這個(gè)return語(yǔ)句,返回“構(gòu)造”后的this對(duì)象询一。
但是隐孽,如果return語(yǔ)句返回的是一個(gè)跟this無(wú)關(guān)的新對(duì)象,new命令會(huì)返回這個(gè)新對(duì)象健蕊,而不是this對(duì)象菱阵。這一點(diǎn)需要特別引起注意。
var Vehicle = function (){
this.price = 1000;
return { price: 2000 };
};
(new Vehicle()).price
// 2000
new.target屬性
函數(shù)內(nèi)部可以使用new.target屬性缩功。如果當(dāng)前函數(shù)是new命令調(diào)用晴及,new.target指向當(dāng)前函數(shù),否則為undefined嫡锌。
function f() {
console.log(new.target === f);
}
f() // false
new f() // true
或者:
function f() {
if (!new.target) {
throw new Error('請(qǐng)使用 new 命令調(diào)用抗俄!');
}
// ...
}
f() // Uncaught Error: 請(qǐng)使用 new 命令調(diào)用!
以現(xiàn)有對(duì)象為模板生成新的對(duì)象: Object.create()
var person1 = {
name: '張三',
age: 38,
greeting: function() {
console.log('Hi! I\'m ' + this.name + '.');
}
};
var person2 = Object.create(person1);
person2.name // 張三
person2.greeting() // Hi! I'm 張三.
this關(guān)鍵字
this
關(guān)鍵字是一個(gè)非常重要的語(yǔ)法點(diǎn)世舰。毫不夸張地說(shuō),不理解它的含義槽卫,大部分開(kāi)發(fā)任務(wù)都無(wú)法完成跟压。
this
用在構(gòu)造函數(shù)中,表示實(shí)例對(duì)象歼培。
不管this
用在什么場(chǎng)合震蒋,共同點(diǎn)都是:返回一個(gè)對(duì)象。
簡(jiǎn)單說(shuō)躲庄,this
就是屬性或方法“當(dāng)前”所在的對(duì)象.
var person = {
name: "BING",
describe: function () {
return "name: " + this.name;
}
}
上面代碼中查剖,this.name
表示name
屬性所在的那個(gè)對(duì)象。由于this.name
是在describe
方法中調(diào)用噪窘,而describe
方法所在的當(dāng)前對(duì)象是person
笋庄,因此this
指向person
,this.name
就是person.name
倔监。
this指向的可變性
由于對(duì)象的屬性可以賦給另一個(gè)對(duì)象直砂,所以屬性所在的當(dāng)前對(duì)象是可變的,即this的指向是可變的浩习。
var A = {
name: '張三',
describe: function () {
return '姓名:'+ this.name;
}
};
var B = {
name: '李四'
};
B.describe = A.describe;
B.describe()
// "姓名:李四"
A.describe() // "姓名:張三"
上面代碼中静暂,A.describe屬性被賦給B,于是B.describe就表示describe方法所在的當(dāng)前對(duì)象是B谱秽,所以this.name就指向B.name洽蛀。
更清晰的表示:
function f() {
return "姓名:" + this.name;
}
var A = {
name: "張三",
describe: f
}
var A = {
name: "李四",
describe: f
}
A.describe() // "姓名:張三"
B.describe() // "姓名:李四"
隨著f所在的對(duì)象不同摹迷,this
的指向也發(fā)生變化。
只要函數(shù)被賦給另一個(gè)變量郊供,this的指向就會(huì)變峡碉。
下面這個(gè)例子更有趣:
var A = {
name: '張三',
describe: function () {
return '姓名:'+ this.name;
}
};
var name = '李四';
var f = A.describe;
f() // "姓名:李四"
上面代碼中,A.describe被賦值給變量f颂碘,內(nèi)部的this就會(huì)指向f運(yùn)行時(shí)所在的對(duì)象(本例是頂層對(duì)象)异赫。
網(wǎng)頁(yè)編程示例:
<input type="text" name="age" size=3 onChange="validate(this, 18, 99);">
<script>
function validate(obj, lowval, hival){
if ((obj.value < lowval) || (obj.value > hival))
console.log('Invalid Value!');
}
</script>
上面代碼是一個(gè)文本輸入框,每當(dāng)用戶輸入一個(gè)值头岔,就會(huì)調(diào)用onChange回調(diào)函數(shù)塔拳,驗(yàn)證這個(gè)值是否在指定范圍。瀏覽器會(huì)向回調(diào)函數(shù)傳入當(dāng)前對(duì)象峡竣,因此this就代表傳入當(dāng)前對(duì)象(即文本框)靠抑,然后就可以從this.value上面讀到用戶的輸入值。
總結(jié)
JS中一切皆對(duì)象适掰,運(yùn)行環(huán)境也是對(duì)象颂碧,函數(shù)是在某個(gè)對(duì)象中運(yùn)行,this
就是函數(shù)運(yùn)行時(shí)所在的對(duì)象类浪。
這個(gè)含義是明確的载城,但是因?yàn)镴S本身支持環(huán)境的動(dòng)態(tài)切換,所以this
的指向是動(dòng)態(tài)的费就,不能事先確定到底指向哪個(gè)對(duì)象诉瓦。
this的實(shí)質(zhì)
var f = function () {
console.log(this.x);
}
var x = 1;
var obj = {
f: f,
x: 2,
};
// 單獨(dú)執(zhí)行
f() // 1
// obj 環(huán)境執(zhí)行
obj.f() // 2
現(xiàn)在問(wèn)題就來(lái)了,由于函數(shù)可以在不同的運(yùn)行環(huán)境執(zhí)行力细,所以需要有一種機(jī)制睬澡,能夠在函數(shù)體內(nèi)部獲得當(dāng)前的運(yùn)行環(huán)境(context)。所以眠蚂,this就出現(xiàn)了煞聪,它的設(shè)計(jì)目的就是在函數(shù)體內(nèi)部,指代函數(shù)當(dāng)前的運(yùn)行環(huán)境逝慧。
this的使用場(chǎng)合
- 全局環(huán)境
- 構(gòu)造函數(shù)
- 對(duì)象的方法
分類上來(lái)說(shuō)就三個(gè)類別昔脯。
全局環(huán)境
this === window // true
function f() {
console.log(this === window);
}
f() // true
構(gòu)造函數(shù)
var Obj = function(p) {
this.p = p;
}
var o = new Obj("hello world!");
o.p; // "hello world!"
上面代碼定義了一個(gè)構(gòu)造函數(shù)Obj。由于this指向?qū)嵗龑?duì)象笛臣,所以在構(gòu)造函數(shù)內(nèi)部定義this.p栅干,就相當(dāng)于定義實(shí)例對(duì)象有一個(gè)p屬性。
對(duì)象的方法
如果對(duì)象的方法中包含this
捐祠,則this
指向的就是方法運(yùn)行時(shí)所在的對(duì)象碱鳞。
將該方法賦值給另一個(gè)對(duì)象,就會(huì)改變this
的指向踱蛀。
var obj ={
foo: function () {
console.log(this);
}
};
obj.foo() // obj
下面三種都改變了this
的指向:
// 情況一
(obj.foo = obj.foo)() // window
// 情況二
(false || obj.foo)() // window
// 情況三
(1, obj.foo)() // window
上面代碼中窿给,obj.foo就是一個(gè)值贵白。這個(gè)值真正調(diào)用的時(shí)候,運(yùn)行環(huán)境已經(jīng)不是obj了崩泡,而是全局環(huán)境禁荒,所以this不再指向obj。
可以這樣理解角撞,JavaScript 引擎內(nèi)部呛伴,obj和obj.foo儲(chǔ)存在兩個(gè)內(nèi)存地址,稱為地址一和地址二谒所。obj.foo()這樣調(diào)用時(shí)热康,是從地址一調(diào)用地址二,因此地址二的運(yùn)行環(huán)境是地址一劣领,this指向obj姐军。但是,上面三種情況尖淘,都是直接取出地址二進(jìn)行調(diào)用奕锌,這樣的話,運(yùn)行環(huán)境就是全局環(huán)境村生,因此this指向全局環(huán)境惊暴。上面三種情況等同于下面的代碼。
// 情況一
(obj.foo = function () {
console.log(this);
})()
// 等同于
(function () {
console.log(this);
})()
// 情況二
(false || function () {
console.log(this);
})()
// 情況三
(1, function () {
console.log(this);
})()
this會(huì)指向最靠近的那個(gè)對(duì)象:
var a = {
p: 'Hello',
b: {
m: function() {
console.log(this.p);
}
}
};
a.b.m() // undefined
改成下面這樣即可:
var a = {
b: {
m: function() {
console.log(this.p);
},
p: 'Hello'
}
};
嵌套對(duì)象內(nèi)部的方法賦值給一個(gè)變量趁桃,會(huì)使得this
指向發(fā)生轉(zhuǎn)移:
var a = {
b: {
m: function() {
console.log(this.p);
},
p: 'Hello'
}
};
var hello = a.b.m;
hello() // undefined
將嵌套對(duì)象內(nèi)部的方法賦值給一個(gè)變量辽话,this依然會(huì)指向全局對(duì)象。
避免多層this
var o = {
f1: function () {
console.log(this);
var f2 = function () {
console.log(this);
}();
}
}
o.f1()
// Object
// Window
上面代碼包含兩層this镇辉,結(jié)果運(yùn)行后,第一層指向?qū)ο髈贴捡,第二層指向全局對(duì)象忽肛,因?yàn)閷?shí)際執(zhí)行的是下面的代碼。
var temp = function () {
console.log(this);
};
var o = {
f1: function () {
console.log(this);
var f2 = temp();
}
}
this的傳遞:
var o = {
f1: function() {
console.log(this);
var that = this;
var f2 = function() {
console.log(that);
}();
}
}
o.f1()
// Object
// Object
上面代碼定義了變量that烂斋,固定指向外層的this屹逛,然后在內(nèi)層使用that,就不會(huì)發(fā)生this指向的改變汛骂。
事實(shí)上罕模,使用一個(gè)變量固定this的值,然后內(nèi)層函數(shù)調(diào)用這個(gè)變量帘瞭,是非常常見(jiàn)的做法淑掌,請(qǐng)務(wù)必掌握。
內(nèi)層this
不指向外部蝶念,而是指向了頂層對(duì)象抛腕。
避免回調(diào)函數(shù)中的this
var o = new Object();
o.f = function () {
console.log(this === o);
}
// jQuery 的寫(xiě)法
$('#button').on('click', o.f);
上面代碼中芋绸,點(diǎn)擊按鈕以后,控制臺(tái)會(huì)顯示false担敌。原因是此時(shí)this不再指向o對(duì)象摔敛,而是指向按鈕的 DOM 對(duì)象,因?yàn)閒方法是在按鈕對(duì)象的環(huán)境中被調(diào)用的全封。這種細(xì)微的差別马昙,很容易在編程中忽視,導(dǎo)致難以察覺(jué)的錯(cuò)誤刹悴。
為了解決這個(gè)問(wèn)題行楞,可以采用下面的一些方法對(duì)this進(jìn)行綁定,也就是使得this固定指向某個(gè)對(duì)象颂跨,減少不確定性敢伸。
綁定 this
this
的動(dòng)態(tài)切換,為JS提供了巨大的靈活性恒削,有時(shí)需要把this
固定下來(lái)池颈。
三種固定方法:
- call
- apply
- bind
call()
var obj = {};
var f = function () {
return this;
};
f() === window // true
f.call(obj) === obj // true
函數(shù)實(shí)例的call方法,可以指定函數(shù)內(nèi)部this的指向(即函數(shù)執(zhí)行時(shí)所在的作用域)钓丰,然后在所指定的作用域中躯砰,調(diào)用該函數(shù)。
注意是在函數(shù)實(shí)例上調(diào)用call
携丁。
function add(a, b) {
return a + b;
}
add.call(this, 1, 2) // 3
call方法還可以接受多個(gè)參數(shù)琢歇,call的第一個(gè)參數(shù)就是this所要指向的那個(gè)對(duì)象,后面的參數(shù)則是函數(shù)調(diào)用時(shí)所需的參數(shù)梦鉴。
apply()
apply方法的作用與call方法類似李茫,也是改變this指向,然后再調(diào)用該函數(shù)肥橙。唯一的區(qū)別就是魄宏,它接收一個(gè)數(shù)組作為函數(shù)執(zhí)行時(shí)的參數(shù),使用格式如下存筏。
func.apply(thisValue, [arg1, arg2, ...])
function f(x, y){
console.log(x + y);
}
f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2
apply方法的第一個(gè)參數(shù)也是this所要指向的那個(gè)對(duì)象宠互,如果設(shè)為null或undefined,則等同于指定全局對(duì)象椭坚。第二個(gè)參數(shù)則是一個(gè)數(shù)組予跌,該數(shù)組的所有成員依次作為參數(shù),傳入原函數(shù)善茎。原函數(shù)的參數(shù)券册,在call方法中必須一個(gè)個(gè)添加,但是在apply方法中,必須以數(shù)組形式添加汁掠。
求數(shù)組的最大元素
var a = [10, 2, 4, 15, 9]
Math.max.apply(null, a);
bind()
var print = d.getTime.bind(d);
print() // 1481869925657
上面代碼中略吨,bind方法將getTime方法內(nèi)部的this綁定到d對(duì)象,這時(shí)就可以安全地將這個(gè)方法賦值給其他變量了考阱。
bind方法的參數(shù)就是所要綁定this的對(duì)象翠忠,下面是一個(gè)更清晰的例子。
var counter = {
count: 0,
inc: function () {
this.count++;
}
};
var func = counter.inc.bind(counter);
func();
counter.count // 1
參考
https://wangdoc.com/javascript/oop/new.html
https://wangdoc.com/javascript/oop/this.html