(建議收藏)原生JS靈魂之問(wèn), 請(qǐng)問(wèn)你能接得住幾個(gè)?(上)

筆者最近在對(duì)原生JS的知識(shí)做系統(tǒng)梳理,因?yàn)槲矣X(jué)得JS作為前端工程師的根本技術(shù),學(xué)再多遍都不為過(guò)。打算來(lái)做一個(gè)系列,一共分三次發(fā),以一系列的問(wèn)題為驅(qū)動(dòng),當(dāng)然也會(huì)有追問(wèn)和擴(kuò)展,內(nèi)容系統(tǒng)且完整,對(duì)初中級(jí)選手會(huì)有很好的提升,高級(jí)選手也會(huì)得到復(fù)習(xí)和鞏固。敬請(qǐng)大家關(guān)注!

第一篇: JS數(shù)據(jù)類型之問(wèn)——概念篇

1.JS原始數(shù)據(jù)類型有哪些?引用數(shù)據(jù)類型有哪些?

在 JS 中,存在著 7 種原始值,分別是:

  • boolean
  • null
  • undefined
  • number
  • string
  • symbol
  • bigint

引用數(shù)據(jù)類型:
對(duì)象Object(包含普通對(duì)象-Object,數(shù)組對(duì)象-Array,正則對(duì)象-RegExp,日期對(duì)象-Date,數(shù)學(xué)函數(shù)-Math,函數(shù)對(duì)象-Function)

2.說(shuō)出下面運(yùn)行的結(jié)果,解釋原因。

function test(person) {
  person.age = 26
  person = {
    name: 'hzj',
    age: 18
  }
  return person
}
const p1 = {
  name: 'fyq',
  age: 19
}
const p2 = test(p1)
console.log(p1) // -> ?
console.log(p2) // -> ?

結(jié)果:

p1:{name: “fyq”, age: 26}
p2:{name: “hzj”, age: 18}

原因: 在函數(shù)傳參的時(shí)候傳遞的是對(duì)象在堆中的內(nèi)存地址值,test函數(shù)中的實(shí)參person是p1對(duì)象的內(nèi)存地址,通過(guò)調(diào)用person.age = 26確實(shí)改變了p1的值,但隨后person變成了另一塊內(nèi)存空間的地址,并且在最后將這另外一份內(nèi)存空間的地址返回,賦給了p2。

3.null是對(duì)象嗎?為什么?

結(jié)論: null不是對(duì)象。

解釋: 雖然 typeof null 會(huì)輸出 object,但是這只是 JS 存在的一個(gè)悠久 Bug。在 JS 的最初版本中使用的是 32 位系統(tǒng),為了性能考慮使用低位存儲(chǔ)變量的類型信息,000 開(kāi)頭代表是對(duì)象然而 null 表示為全零,所以將它錯(cuò)誤的判斷為 object 。

4.'1'.toString()為什么可以調(diào)用?

其實(shí)在這個(gè)語(yǔ)句運(yùn)行的過(guò)程中做了這樣幾件事情:

var s = new Object('1');
s.toString();
s = null;

第一步: 創(chuàng)建Object類實(shí)例。注意為什么不是String ? 由于Symbol和BigInt的出現(xiàn),對(duì)它們調(diào)用new都會(huì)報(bào)錯(cuò),目前ES6規(guī)范也不建議用new來(lái)創(chuàng)建基本類型的包裝類。

第二步: 調(diào)用實(shí)例方法。

第三步: 執(zhí)行完方法立即銷毀這個(gè)實(shí)例。

整個(gè)過(guò)程體現(xiàn)了基本包裝類型的性質(zhì),而基本包裝類型恰恰屬于基本數(shù)據(jù)類型,包括Boolean, Number和String。

參考:《JavaScript高級(jí)程序設(shè)計(jì)(第三版)》P118

5.0.1+0.2為什么不等于0.3?

0.1和0.2在轉(zhuǎn)換成二進(jìn)制后會(huì)無(wú)限循環(huán),由于標(biāo)準(zhǔn)位數(shù)的限制后面多余的位數(shù)會(huì)被截掉,此時(shí)就已經(jīng)出現(xiàn)了精度的損失,相加后因浮點(diǎn)數(shù)小數(shù)位的限制而截?cái)嗟亩M(jìn)制數(shù)字在轉(zhuǎn)換為十進(jìn)制就會(huì)變成0.30000000000000004。

6.如何理解BigInt?

什么是BigInt?

BigInt是一種新的數(shù)據(jù)類型,用于當(dāng)整數(shù)值大于Number數(shù)據(jù)類型支持的范圍時(shí)。這種數(shù)據(jù)類型允許我們安全地對(duì)大整數(shù)執(zhí)行算術(shù)操作,表示高分辨率的時(shí)間戳,使用大整數(shù)id,等等,而不需要使用庫(kù)。

為什么需要BigInt?

在JS中,所有的數(shù)字都以雙精度64位浮點(diǎn)格式表示,那這會(huì)帶來(lái)什么問(wèn)題呢?

這導(dǎo)致JS中的Number無(wú)法精確表示非常大的整數(shù),它會(huì)將非常大的整數(shù)四舍五入,確切地說(shuō),JS中的Number類型只能安全地表示-9007199254740991(-(253-1))和9007199254740991((253-1)),任何超出此范圍的整數(shù)值都可能失去精度。

console.log(999999999999999);  //=>10000000000000000

同時(shí)也會(huì)有一定的安全性問(wèn)題:

9007199254740992 === 9007199254740993;    // → true 居然是true!

如何創(chuàng)建并使用BigInt?

要?jiǎng)?chuàng)建BigInt,只需要在數(shù)字末尾追加n即可。

console.log( 9007199254740995n );    // → 9007199254740995n 
console.log( 9007199254740995 );     // → 9007199254740996

另一種創(chuàng)建BigInt的方法是用BigInt()構(gòu)造函數(shù)、

BigInt("9007199254740995");    // → 9007199254740995n

簡(jiǎn)單使用如下:

10n + 20n;    // → 30n  
10n - 20n;    // → -10n 
+10n;         // → TypeError: Cannot convert a BigInt value to a number 
-10n;         // → -10n 
10n * 20n;    // → 200n 
20n / 10n;    // → 2n   
23n % 10n;    // → 3n   
10n ** 3n;    // → 1000n    

const x = 10n;  
++x;          // → 11n  
--x;          // → 9n
console.log(typeof x);   //"bigint"

值得警惕的點(diǎn)

  1. BigInt不支持一元加號(hào)運(yùn)算符, 這可能是某些程序可能依賴于 + 始終生成 Number 的不變量,或者拋出異常。另外,更改 + 的行為也會(huì)破壞 asm.js代碼。

  2. 因?yàn)殡[式類型轉(zhuǎn)換可能丟失信息,所以不允許在bigint和 Number 之間進(jìn)行混合操作。當(dāng)混合使用大整數(shù)和浮點(diǎn)數(shù)時(shí),結(jié)果值可能無(wú)法由BigInt或Number精確表示。

10 + 10n;    // → TypeError
  1. 不能將BigInt傳遞給Web api和內(nèi)置的 JS 函數(shù),這些函數(shù)需要一個(gè) Number 類型的數(shù)字。嘗試這樣做會(huì)報(bào)TypeError錯(cuò)誤。
Math.max(2n, 4n, 6n);    // → TypeError
  1. 當(dāng) Boolean 類型與 BigInt 類型相遇時(shí),BigInt的處理方式與Number類似,換句話說(shuō),只要不是0n,BigInt就被視為truthy的值。
if(0n){//條件判斷為false

}
if(3n){//條件為true

}
  1. 元素都為BigInt的數(shù)組可以進(jìn)行sort。

  2. BigInt可以正常地進(jìn)行位運(yùn)算,如|、&、<<、>>和^

瀏覽器兼容性

caniuse的結(jié)果:

image

其實(shí)現(xiàn)在的兼容性并不怎么好,只有chrome67、firefox、Opera這些主流實(shí)現(xiàn),要正式成為規(guī)范,其實(shí)還有很長(zhǎng)的路要走。

我們期待BigInt的光明前途!

第二篇: JS數(shù)據(jù)類型之問(wèn)——檢測(cè)篇

1. typeof 是否能正確判斷類型?

對(duì)于原始類型來(lái)說(shuō),除了 null 都可以調(diào)用typeof顯示正確的類型。

typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'

但對(duì)于引用數(shù)據(jù)類型,除了函數(shù)之外,都會(huì)顯示"object"。

typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'

因此采用typeof判斷對(duì)象數(shù)據(jù)類型是不合適的,采用instanceof會(huì)更好,instanceof的原理是基于原型鏈的查詢,只要處于原型鏈中,判斷永遠(yuǎn)為true

const Person = function() {}
const p1 = new Person()
p1 instanceof Person // true

var str1 = 'hello world'
str1 instanceof String // false

var str2 = new String('hello world')
str2 instanceof String // true

2. instanceof能否判斷基本數(shù)據(jù)類型?

能。比如下面這種方式:

class PrimitiveNumber {
  static [Symbol.hasInstance](x) {
    return typeof x === 'number'
  }
}
console.log(111 instanceof PrimitiveNumber) // true

如果你不知道Symbol,可以看看MDN上關(guān)于hasInstance的解釋

其實(shí)就是自定義instanceof行為的一種方式,這里將原有的instanceof方法重定義,換成了typeof,因此能夠判斷基本數(shù)據(jù)類型。

3. 能不能手動(dòng)實(shí)現(xiàn)一下instanceof的功能?

核心: 原型鏈的向上查找。

function myInstanceof(left, right) {
    //基本數(shù)據(jù)類型直接返回false
    if(typeof left !== 'object' || left === null) return false;
    //getProtypeOf是Object對(duì)象自帶的一個(gè)方法,能夠拿到參數(shù)的原型對(duì)象
    let proto = Object.getPrototypeOf(left);
    while(true) {
        //查找到盡頭,還沒(méi)找到
        if(proto == null) return false;
        //找到相同的原型對(duì)象
        if(proto == right.prototype) return true;
        proto = Object.getPrototypeof(proto);
    }
}

測(cè)試:

console.log(myInstanceof("111", String)); //false
console.log(myInstanceof(new String("111"), String));//true

4. Object.is和===的區(qū)別?

Object在嚴(yán)格等于的基礎(chǔ)上修復(fù)了一些特殊情況下的失誤,具體來(lái)說(shuō)就是+0和-0,NaN和NaN。
源碼如下:


function is(x, y) {
  if (x === y) {
    //運(yùn)行到1/x === 1/y的時(shí)候x和y都為0,但是1/+0 = +Infinity, 1/-0 = -Infinity, 是不一樣的
    return x !== 0 || y !== 0 || 1 / x === 1 / y;
  } else {
    //NaN===NaN是false,這是不對(duì)的,我們?cè)谶@里做一個(gè)攔截,x !== x,那么一定是 NaN, y 同理
    //兩個(gè)都是NaN的時(shí)候返回true
    return x !== x && y !== y;
  }
 

第三篇: JS數(shù)據(jù)類型之問(wèn)——轉(zhuǎn)換篇

1. [] == ![]結(jié)果是什么?為什么?

解析:

== 中,左右兩邊都需要轉(zhuǎn)換為數(shù)字然后進(jìn)行比較。

[]轉(zhuǎn)換為數(shù)字為0。

![] 首先是轉(zhuǎn)換為布爾值,由于[]作為一個(gè)引用類型轉(zhuǎn)換為布爾值為true,

因此![]為false,進(jìn)而在轉(zhuǎn)換成數(shù)字,變?yōu)?。

0 == 0 , 結(jié)果為true

2. JS中類型轉(zhuǎn)換有哪幾種?

JS中,類型轉(zhuǎn)換只有三種:

  • 轉(zhuǎn)換成數(shù)字
  • 轉(zhuǎn)換成布爾值
  • 轉(zhuǎn)換成字符串

轉(zhuǎn)換具體規(guī)則如下:

注意"Boolean 轉(zhuǎn)字符串"這行結(jié)果指的是 true 轉(zhuǎn)字符串的例子

[圖片上傳失敗...(image-ce018a-1572492788692)]

3. == 和 ===有什么區(qū)別?

===叫做嚴(yán)格相等,是指:左右兩邊不僅值要相等,類型也要相等,例如'1'===1的結(jié)果是false,因?yàn)橐贿吺莝tring,另一邊是number。

==不像===那樣嚴(yán)格,對(duì)于一般情況,只要值相等,就返回true,但==還涉及一些類型轉(zhuǎn)換,它的轉(zhuǎn)換規(guī)則如下:

  • 兩邊的類型是否相同,相同的話就比較值的大小,例如1==2,返回false
  • 判斷的是否是null和undefined,是的話就返回true
  • 判斷的類型是否是String和Number,是的話,把String類型轉(zhuǎn)換成Number,再進(jìn)行比較
  • 判斷其中一方是否是Boolean,是的話就把Boolean轉(zhuǎn)換成Number,再進(jìn)行比較
  • 如果其中一方為Object,且另一方為String、Number或者Symbol,會(huì)將Object轉(zhuǎn)換成字符串,再進(jìn)行比較
console.log({a: 1} == true);//false
console.log({a: 1} == "[object Object]");//true

4. 對(duì)象轉(zhuǎn)原始類型是根據(jù)什么流程運(yùn)行的?

對(duì)象轉(zhuǎn)原始類型,會(huì)調(diào)用內(nèi)置的[ToPrimitive]函數(shù),對(duì)于該函數(shù)而言,其邏輯如下:

  1. 如果Symbol.toPrimitive()方法,優(yōu)先調(diào)用再返回
  2. 調(diào)用valueOf(),如果轉(zhuǎn)換為原始類型,則返回
  3. 調(diào)用toString(),如果轉(zhuǎn)換為原始類型,則返回
  4. 如果都沒(méi)有返回原始類型,會(huì)報(bào)錯(cuò)
var obj = {
  value: 3,
  valueOf() {
    return 4;
  },
  toString() {
    return '5'
  },
  [Symbol.toPrimitive]() {
    return 6
  }
}
console.log(obj + 1); // 輸出7

5. 如何讓if(a == 1 && a == 2)條件成立?

其實(shí)就是上一個(gè)問(wèn)題的應(yīng)用。

var a = {
  value: 0,
  valueOf: function() {
    this.value++;
    return this.value;
  }
};
console.log(a == 1 && a == 2);//true

第四篇: 談?wù)勀銓?duì)閉包的理解

什么是閉包?

紅寶書(shū)(p178)上對(duì)于閉包的定義:閉包是指有權(quán)訪問(wèn)另外一個(gè)函數(shù)作用域中的變量的函數(shù),
MDN 對(duì)閉包的定義為:閉包是指那些能夠訪問(wèn)自由變量的函數(shù)。

(其中自由變量,指在函數(shù)中使用的,但既不是函數(shù)參數(shù)arguments也不是函數(shù)的局部變量的變量,其實(shí)就是另外一個(gè)函數(shù)作用域中的變量。)

閉包產(chǎn)生的原因?

首先要明白作用域鏈的概念,其實(shí)很簡(jiǎn)單,在ES5中只存在兩種作用域————全局作用域和函數(shù)作用域,當(dāng)訪問(wèn)一個(gè)變量時(shí),解釋器會(huì)首先在當(dāng)前作用域查找標(biāo)示符,如果沒(méi)有找到,就去父作用域找,直到找到該變量的標(biāo)示符或者不在父作用域中,這就是作用域鏈,值得注意的是,每一個(gè)子函數(shù)都會(huì)拷貝上級(jí)的作用域,形成一個(gè)作用域的鏈條。 比如:

var a = 1;
function f1() {
  var a = 2
  function f2() {
    var a = 3;
    console.log(a);//3
  }
}

在這段代碼中,f1的作用域指向有全局作用域(window)和它本身,而f2的作用域指向全局作用域(window)、f1和它本身。而且作用域是從最底層向上找,直到找到全局作用域window為止,如果全局還沒(méi)有的話就會(huì)報(bào)錯(cuò)。就這么簡(jiǎn)單一件事情!

閉包產(chǎn)生的本質(zhì)就是,當(dāng)前環(huán)境中存在指向父級(jí)作用域的引用。還是舉上面的例子:

function f1() {
  var a = 2
  function f2() {
    console.log(a);//2
  }
  return f2;
}
var x = f1();
x();

這里x會(huì)拿到父級(jí)作用域中的變量,輸出2。因?yàn)樵诋?dāng)前環(huán)境中,含有對(duì)f2的引用,f2恰恰引用了window、f1和f2的作用域。因此f2可以訪問(wèn)到f1的作用域的變量。

那是不是只有返回函數(shù)才算是產(chǎn)生了閉包呢?、

回到閉包的本質(zhì),我們只需要讓父級(jí)作用域的引用存在即可,因此我們還可以這么做:

var f3;
function f1() {
  var a = 2
  f3 = function() {
    console.log(a);
  }
}
f1();
f3();

讓f1執(zhí)行,給f3賦值后,等于說(shuō)現(xiàn)在f3擁有了window、f1和f3本身這幾個(gè)作用域的訪問(wèn)權(quán)限,還是自底向上查找,最近是在f1中找到了a,因此輸出2。

在這里是外面的變量f3存在著父級(jí)作用域的引用,因此產(chǎn)生了閉包,形式變了,本質(zhì)沒(méi)有改變。

閉包有哪些表現(xiàn)形式?

明白了本質(zhì)之后,我們就來(lái)看看,在真實(shí)的場(chǎng)景中,究竟在哪些地方能體現(xiàn)閉包的存在?

  1. 返回一個(gè)函數(shù)。剛剛已經(jīng)舉例。
  2. 作為函數(shù)參數(shù)傳遞
var a = 1;
function foo(){
  var a = 2;
  function baz(){
    console.log(a);
  }
  bar(baz);
}
function bar(fn){
  // 這就是閉包
  fn();
}
// 輸出2,而不是1
foo();
  1. 在定時(shí)器、事件監(jiān)聽(tīng)、Ajax請(qǐng)求、跨窗口通信、Web Workers或者任何異步中,只要使用了回調(diào)函數(shù),實(shí)際上就是在使用閉包。

以下的閉包保存的僅僅是window和當(dāng)前作用域。

// 定時(shí)器
setTimeout(function timeHandler(){
  console.log('111');
},100)

// 事件監(jiān)聽(tīng)
$('#app').click(function(){
  console.log('DOM Listener');
})
  1. IIFE(立即執(zhí)行函數(shù)表達(dá)式)創(chuàng)建閉包, 保存了全局作用域window當(dāng)前函數(shù)的作用域,因此可以全局的變量。
var a = 2;
(function IIFE(){
  // 輸出2
  console.log(a);
})();

如何解決下面的循環(huán)輸出問(wèn)題?

for(var i = 1; i <= 5; i ++){
  setTimeout(function timer(){
    console.log(i)
  }, 0)
}

為什么會(huì)全部輸出6?如何改進(jìn),讓它輸出1,2,3,4,5?(方法越多越好)

因?yàn)閟etTimeout為宏任務(wù),由于JS中單線程eventLoop機(jī)制,在主線程同步任務(wù)執(zhí)行完后才去執(zhí)行宏任務(wù),因此循環(huán)結(jié)束后setTimeout中的回調(diào)才依次執(zhí)行,但輸出i的時(shí)候當(dāng)前作用域沒(méi)有,往上一級(jí)再找,發(fā)現(xiàn)了i,此時(shí)循環(huán)已經(jīng)結(jié)束,i變成了6。因此會(huì)全部輸出6。

解決方法:

1、利用IIFE(立即執(zhí)行函數(shù)表達(dá)式)當(dāng)每次for循環(huán)時(shí),把此時(shí)的i變量傳遞到定時(shí)器中

for(var i = 1;i <= 5;i++){
  (function(j){
    setTimeout(function timer(){
      console.log(j)
    }, 0)
  })(i)
}

2、給定時(shí)器傳入第三個(gè)參數(shù), 作為timer函數(shù)的第一個(gè)函數(shù)參數(shù)

for(var i=1;i<=5;i++){
  setTimeout(function timer(j){
    console.log(j)
  }, 0, i)
}

3、使用ES6中的let

for(let i = 1; i <= 5; i++){
  setTimeout(function timer(){
    console.log(i)
  },0)
}

let使JS發(fā)生革命性的變化,讓JS有函數(shù)作用域變?yōu)榱藟K級(jí)作用域,用let后作用域鏈不復(fù)存在。代碼的作用域以塊級(jí)為單位,以上面代碼為例:

// i = 1
{
  setTimeout(function timer(){
    console.log(1)
  },0)
}
// i = 2
{
  setTimeout(function timer(){
    console.log(2)
  },0)
}
// i = 3
...

因此能輸出正確的結(jié)果。

第五篇: 談?wù)勀銓?duì)原型鏈的理解

1.原型對(duì)象和構(gòu)造函數(shù)有何關(guān)系?

在JavaScript中,每當(dāng)定義一個(gè)函數(shù)數(shù)據(jù)類型(普通函數(shù)、類)時(shí)候,都會(huì)天生自帶一個(gè)prototype屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象。

當(dāng)函數(shù)經(jīng)過(guò)new調(diào)用時(shí),這個(gè)函數(shù)就成為了構(gòu)造函數(shù),返回一個(gè)全新的實(shí)例對(duì)象,這個(gè)實(shí)例對(duì)象有一個(gè)proto屬性,指向構(gòu)造函數(shù)的原型對(duì)象。

image

2.能不能描述一下原型鏈?

JavaScript對(duì)象通過(guò)prototype指向父類對(duì)象,直到指向Object對(duì)象為止,這樣就形成了一個(gè)原型指向的鏈條, 即原型鏈。

image
  • 對(duì)象的 hasOwnProperty() 來(lái)檢查對(duì)象自身中是否含有該屬性
  • 使用 in 檢查對(duì)象中是否含有某個(gè)屬性時(shí),如果對(duì)象中沒(méi)有但是原型鏈中有,也會(huì)返回 true

第六篇: JS如何實(shí)現(xiàn)繼承?

第一種: 借助call

  function Parent1(){
    this.name = 'parent1';
  }
  function Child1(){
    Parent1.call(this);
    this.type = 'child1'
  }
  console.log(new Child1);

這樣寫(xiě)的時(shí)候子類雖然能夠拿到父類的屬性值,但是問(wèn)題是父類原型對(duì)象中一旦存在方法那么子類無(wú)法繼承。那么引出下面的方法。

第二種: 借助原型鏈

  function Parent2() {
    this.name = 'parent2';
    this.play = [1, 2, 3]
  }
  function Child2() {
    this.type = 'child2';
  }
  Child2.prototype = new Parent2();

  console.log(new Child2());

看似沒(méi)有問(wèn)題,父類的方法和屬性都能夠訪問(wèn),但實(shí)際上有一個(gè)潛在的不足。舉個(gè)例子:

  var s1 = new Child2();
  var s2 = new Child2();
  s1.play.push(4);
  console.log(s1.play, s2.play);

可以看到控制臺(tái):

[圖片上傳失敗...(image-e83e3d-1572492788692)]

明明我只改變了s1的play屬性,為什么s2也跟著變了呢?很簡(jiǎn)單,因?yàn)閮蓚€(gè)實(shí)例使用的是同一個(gè)原型對(duì)象。

那么還有更好的方式么?

第三種:將前兩種組合

  function Parent3 () {
    this.name = 'parent3';
    this.play = [1, 2, 3];
  }
  function Child3() {
    Parent3.call(this);
    this.type = 'child3';
  }
  Child3.prototype = new Parent3();
  var s3 = new Child3();
  var s4 = new Child3();
  s3.play.push(4);
  console.log(s3.play, s4.play);

可以看到控制臺(tái):

[圖片上傳失敗...(image-2e3c13-1572492788692)]

之前的問(wèn)題都得以解決。但是這里又徒增了一個(gè)新問(wèn)題,那就是Parent3的構(gòu)造函數(shù)會(huì)多執(zhí)行了一次(Child3.prototype = new Parent3();)。這是我們不愿看到的。那么如何解決這個(gè)問(wèn)題?

第四種: 組合繼承的優(yōu)化1

  function Parent4 () {
    this.name = 'parent4';
    this.play = [1, 2, 3];
  }
  function Child4() {
    Parent4.call(this);
    this.type = 'child4';
  }
  Child4.prototype = Parent4.prototype;

這里讓將父類原型對(duì)象直接給到子類,父類構(gòu)造函數(shù)只執(zhí)行一次,而且父類屬性和方法均能訪問(wèn),但是我們來(lái)測(cè)試一下:

  var s3 = new Child4();
  var s4 = new Child4();
  console.log(s3)

[圖片上傳失敗...(image-413287-1572492788692)]

子類實(shí)例的構(gòu)造函數(shù)是Parent4,顯然這是不對(duì)的,應(yīng)該是Child4。

第五種(最推薦使用): 組合繼承的優(yōu)化1

  function Parent5 () {
    this.name = 'parent5';
    this.play = [1, 2, 3];
  }
  function Child5() {
    Parent5.call(this);
    this.type = 'child5';
  }
  Child5.prototype = Object.create(Parent5.prototype);
  Child5.prototype.constructor = Child5;

這是最推薦的一種方式,接近完美的繼承,它的名字也叫做寄生組合繼承。

ES6的extends被編譯后的JavaScript代碼

ES6的代碼最后都是要在瀏覽器上能夠跑起來(lái)的,這中間就利用了babel這個(gè)編譯工具,將ES6的代碼編譯成ES5讓一些不支持新語(yǔ)法的瀏覽器也能運(yùn)行。

那最后編譯成了什么樣子呢?

function _possibleConstructorReturn (self, call) { 
        // ...
        return call && (typeof call === 'object' || typeof call === 'function') ? call : self; 
}

function _inherits (subClass, superClass) { 
    // ...
    //看到?jīng)]有
        subClass.prototype = Object.create(superClass && superClass.prototype, { 
                constructor: { 
                        value: subClass, 
                        enumerable: false, 
                        writable: true, 
                        configurable: true 
                } 
        }); 
        if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
}


var Parent = function Parent () {
        // 驗(yàn)證是否是 Parent 構(gòu)造出來(lái)的 this
        _classCallCheck(this, Parent);
};

var Child = (function (_Parent) {
        _inherits(Child, _Parent);

        function Child () {
                _classCallCheck(this, Child);
        
                return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));
        }

        return Child;
}(Parent));

核心是_inherits函數(shù),可以看到它采用的依然也是第五種方式————寄生組合繼承方式,同時(shí)證明了這種方式的成功。不過(guò)這里加了一個(gè)Object.setPrototypeOf(subClass, superClass),這是用來(lái)干啥的呢?

答案是用來(lái)繼承父類的靜態(tài)方法。這也是原來(lái)的繼承方式疏忽掉的地方。

追問(wèn): 面向?qū)ο蟮脑O(shè)計(jì)一定是好的設(shè)計(jì)嗎?

不一定。從繼承的角度說(shuō),這一設(shè)計(jì)是存在巨大隱患的。

從設(shè)計(jì)思想上談?wù)劺^承本身的問(wèn)題

假如現(xiàn)在有不同品牌的車,每輛車都有drive、music、addOil這三個(gè)方法。

class Car{
  constructor(id) {
    this.id = id;
  }
  drive(){
    console.log("wuwuwu!");
  }
  music(){
    console.log("lalala!")
  }
  addOil(){
    console.log("哦喲!")
  }
}
class otherCar extends Car{}

現(xiàn)在可以實(shí)現(xiàn)車的功能,并且以此去擴(kuò)展不同的車。

但是問(wèn)題來(lái)了,新能源汽車也是車,但是它并不需要addOil(加油)。

如果讓新能源汽車的類繼承Car的話,也是有問(wèn)題的,俗稱"大猩猩和香蕉"的問(wèn)題。大猩猩手里有香蕉,但是我現(xiàn)在明明只需要香蕉,卻拿到了一只大猩猩。也就是說(shuō)加油這個(gè)方法,我現(xiàn)在是不需要的,但是由于繼承的原因,也給到子類了。

繼承的最大問(wèn)題在于:無(wú)法決定繼承哪些屬性,所有屬性都得繼承。

當(dāng)然你可能會(huì)說(shuō),可以再創(chuàng)建一個(gè)父類啊,把加油的方法給去掉,但是這也是有問(wèn)題的,一方面父類是無(wú)法描述所有子類的細(xì)節(jié)情況的,為了不同的子類特性去增加不同的父類,代碼勢(shì)必會(huì)大量重復(fù),另一方面一旦子類有所變動(dòng),父類也要進(jìn)行相應(yīng)的更新,代碼的耦合性太高,維護(hù)性不好。

那如何來(lái)解決繼承的諸多問(wèn)題呢?

用組合,這也是當(dāng)今編程語(yǔ)法發(fā)展的趨勢(shì),比如golang完全采用的是面向組合的設(shè)計(jì)方式。

顧名思義,面向組合就是先設(shè)計(jì)一系列零件,然后將這些零件進(jìn)行拼裝,來(lái)形成不同的實(shí)例或者類。

function drive(){
  console.log("wuwuwu!");
}
function music(){
  console.log("lalala!")
}
function addOil(){
  console.log("哦喲!")
}

let car = compose(drive, music, addOil);
let newEnergyCar = compose(drive, music);

代碼干凈,復(fù)用性也很好。這就是面向組合的設(shè)計(jì)方式。

參考出處:

ES5實(shí)現(xiàn)繼承那些事

重學(xué)JS系列:聊聊繼承

JS最新基本數(shù)據(jù)類型:BigInt(譯)

yck前端面試之道

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,415評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,104評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,884評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,647評(píng)論 6 408
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,130評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,366評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,887評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,737評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,939評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,174評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,586評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,827評(píng)論 1 283
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,608評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,914評(píng)論 2 372

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

  • 第3章 基本概念 3.1 語(yǔ)法 3.2 關(guān)鍵字和保留字 3.3 變量 3.4 數(shù)據(jù)類型 5種簡(jiǎn)單數(shù)據(jù)類型:Unde...
    RickCole閱讀 5,144評(píng)論 0 21
  • 第一章 錯(cuò)誤處理: 錯(cuò)誤: 程序運(yùn)行過(guò)程中,導(dǎo)致程序無(wú)法正常執(zhí)行的現(xiàn)象(即bug) 現(xiàn)象: 程序一旦出錯(cuò),默認(rèn)會(huì)報(bào)...
    fastwe閱讀 1,132評(píng)論 0 1
  • 概要 64學(xué)時(shí) 3.5學(xué)分 章節(jié)安排 電子商務(wù)網(wǎng)站概況 HTML5+CSS3 JavaScript Node 電子...
    阿啊阿吖丁閱讀 9,264評(píng)論 0 3
  • JavaScript語(yǔ)言精粹 前言 約定:=> 表示參考相關(guān)文章或書(shū)籍; JS是JavaScript的縮寫(xiě)。 本書(shū)...
    微笑的AK47閱讀 585評(píng)論 0 3
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,120評(píng)論 1 32