2019-05-07 OOP總結(jié)

[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指向personthis.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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末乞榨,一起剝皮案震驚了整個(gè)濱河市秽之,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吃既,老刑警劉巖考榨,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異鹦倚,居然都是意外死亡河质,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門震叙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)掀鹅,“玉大人,你說(shuō)我怎么就攤上這事媒楼±肿穑” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵划址,是天一觀的道長(zhǎng)扔嵌。 經(jīng)常有香客問(wèn)我,道長(zhǎng)夺颤,這世上最難降的妖魔是什么痢缎? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮世澜,結(jié)果婚禮上独旷,老公的妹妹穿的比我還像新娘。我一直安慰自己宜狐,他們只是感情好势告,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布蛇捌。 她就那樣靜靜地躺著抚恒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪络拌。 梳的紋絲不亂的頭發(fā)上俭驮,一...
    開(kāi)封第一講書(shū)人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼混萝。 笑死遗遵,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的逸嘀。 我是一名探鬼主播车要,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼崭倘!你這毒婦竟也來(lái)了翼岁?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤司光,失蹤者是張志新(化名)和其女友劉穎琅坡,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體残家,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡榆俺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了坞淮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茴晋。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖碾盐,靈堂內(nèi)的尸體忽然破棺而出晃跺,到底是詐尸還是另有隱情,我是刑警寧澤毫玖,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布掀虎,位于F島的核電站,受9級(jí)特大地震影響付枫,放射性物質(zhì)發(fā)生泄漏烹玉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一阐滩、第九天 我趴在偏房一處隱蔽的房頂上張望二打。 院中可真熱鬧,春花似錦掂榔、人聲如沸继效。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瑞信。三九已至,卻和暖如春穴豫,著一層夾襖步出監(jiān)牢的瞬間凡简,已是汗流浹背逼友。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秤涩,地道東北人帜乞。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像筐眷,于是被迫代替她去往敵國(guó)和親黎烈。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容