(建議收藏)原生JS靈魂之問, 請問你能接得住幾個?(上)

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

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

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

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

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

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

2.說出下面運行的結(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ù)傳參的時候傳遞的是對象在堆中的內(nèi)存地址值,test函數(shù)中的實參person是p1對象的內(nèi)存地址,通過調(diào)用person.age = 26確實改變了p1的值,但隨后person變成了另一塊內(nèi)存空間的地址,并且在最后將這另外一份內(nèi)存空間的地址返回,賦給了p2。

3.null是對象嗎?為什么?

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

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

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

其實在這個語句運行的過程中做了這樣幾件事情:

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

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

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

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

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

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

5.0.1+0.2為什么不等于0.3?

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

6.如何理解BigInt?

什么是BigInt?

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

為什么需要BigInt?

在JS中,所有的數(shù)字都以雙精度64位浮點格式表示,那這會帶來什么問題呢?

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

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

同時也會有一定的安全性問題:

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

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

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

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

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

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

簡單使用如下:

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"

值得警惕的點

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

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

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

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

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

  2. BigInt可以正常地進行位運算,如|、&、<<、>>和^

瀏覽器兼容性

caniuse的結(jié)果:

image

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

我們期待BigInt的光明前途!

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

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

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

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

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

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

因此采用typeof判斷對象數(shù)據(jù)類型是不合適的,采用instanceof會更好,instanceof的原理是基于原型鏈的查詢,只要處于原型鏈中,判斷永遠為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的解釋。

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

3. 能不能手動實現(xiàn)一下instanceof的功能?

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

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

測試:

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

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

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


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

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

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

解析:

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

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

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

因此![]為false,進而在轉(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,因為一邊是string,另一邊是number。

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

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

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

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

  1. 如果Symbol.toPrimitive()方法,優(yōu)先調(diào)用再返回
  2. 調(diào)用valueOf(),如果轉(zhuǎn)換為原始類型,則返回
  3. 調(diào)用toString(),如果轉(zhuǎn)換為原始類型,則返回
  4. 如果都沒有返回原始類型,會報錯
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)條件成立?

其實就是上一個問題的應(yīng)用。

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

第四篇: 談?wù)勀銓﹂]包的理解

什么是閉包?

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

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

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

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

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

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

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

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

這里x會拿到父級作用域中的變量,輸出2。因為在當(dāng)前環(huán)境中,含有對f2的引用,f2恰恰引用了window、f1和f2的作用域。因此f2可以訪問到f1的作用域的變量。

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

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

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

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

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

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

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

  1. 返回一個函數(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. 在定時器、事件監(jiān)聽、Ajax請求、跨窗口通信、Web Workers或者任何異步中,只要使用了回調(diào)函數(shù),實際上就是在使用閉包。

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

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

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

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

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

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

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

解決方法:

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

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

2、給定時器傳入第三個參數(shù), 作為timer函數(shù)的第一個函數(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級作用域,用let后作用域鏈不復(fù)存在。代碼的作用域以塊級為單位,以上面代碼為例:

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

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

第五篇: 談?wù)勀銓υ玩湹睦斫?/h2>

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

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

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

image

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

JavaScript對象通過prototype指向父類對象,直到指向Object對象為止,這樣就形成了一個原型指向的鏈條, 即原型鏈。

image
  • 對象的 hasOwnProperty() 來檢查對象自身中是否含有該屬性
  • 使用 in 檢查對象中是否含有某個屬性時,如果對象中沒有但是原型鏈中有,也會返回 true

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

第一種: 借助call

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

這樣寫的時候子類雖然能夠拿到父類的屬性值,但是問題是父類原型對象中一旦存在方法那么子類無法繼承。那么引出下面的方法。

第二種: 借助原型鏈

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

  console.log(new Child2());

看似沒有問題,父類的方法和屬性都能夠訪問,但實際上有一個潛在的不足。舉個例子:

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

可以看到控制臺:

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

明明我只改變了s1的play屬性,為什么s2也跟著變了呢?很簡單,因為兩個實例使用的是同一個原型對象。

那么還有更好的方式么?

第三種:將前兩種組合

  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);

可以看到控制臺:

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

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

第四種: 組合繼承的優(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;

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

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

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

子類實例的構(gòu)造函數(shù)是Parent4,顯然這是不對的,應(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的代碼最后都是要在瀏覽器上能夠跑起來的,這中間就利用了babel這個編譯工具,將ES6的代碼編譯成ES5讓一些不支持新語法的瀏覽器也能運行。

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

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 () {
        // 驗證是否是 Parent 構(gòu)造出來的 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ù),可以看到它采用的依然也是第五種方式————寄生組合繼承方式,同時證明了這種方式的成功。不過這里加了一個Object.setPrototypeOf(subClass, superClass),這是用來干啥的呢?

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

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

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

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

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

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

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

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

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

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

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

那如何來解決繼承的諸多問題呢?

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

顧名思義,面向組合就是先設(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è)計方式。

參考出處:

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

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

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

yck前端面試之道

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容