筆者最近在對原生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"
值得警惕的點
BigInt不支持一元加號運算符, 這可能是某些程序可能依賴于 + 始終生成 Number 的不變量,或者拋出異常。另外,更改 + 的行為也會破壞 asm.js代碼。
因為隱式類型轉(zhuǎn)換可能丟失信息,所以不允許在bigint和 Number 之間進行混合操作。當(dāng)混合使用大整數(shù)和浮點數(shù)時,結(jié)果值可能無法由BigInt或Number精確表示。
10 + 10n; // → TypeError
- 不能將BigInt傳遞給Web api和內(nèi)置的 JS 函數(shù),這些函數(shù)需要一個 Number 類型的數(shù)字。嘗試這樣做會報TypeError錯誤。
Math.max(2n, 4n, 6n); // → TypeError
- 當(dāng) Boolean 類型與 BigInt 類型相遇時,BigInt的處理方式與Number類似,換句話說,只要不是0n,BigInt就被視為truthy的值。
if(0n){//條件判斷為false
}
if(3n){//條件為true
}
元素都為BigInt的數(shù)組可以進行sort。
BigInt可以正常地進行位運算,如|、&、<<、>>和^
瀏覽器兼容性
caniuse的結(jié)果:
其實現(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ù)而言,其邏輯如下:
- 如果Symbol.toPrimitive()方法,優(yōu)先調(diào)用再返回
- 調(diào)用valueOf(),如果轉(zhuǎn)換為原始類型,則返回
- 調(diào)用toString(),如果轉(zhuǎn)換為原始類型,則返回
- 如果都沒有返回原始類型,會報錯
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)閉包的存在?
- 返回一個函數(shù)。剛剛已經(jīng)舉例。
- 作為函數(shù)參數(shù)傳遞
var a = 1;
function foo(){
var a = 2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
// 這就是閉包
fn();
}
// 輸出2,而不是1
foo();
- 在定時器、事件監(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');
})
- 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ù)的原型對象。
2.能不能描述一下原型鏈?
JavaScript對象通過prototype指向父類對象,直到指向Object對象為止,這樣就形成了一個原型指向的鏈條, 即原型鏈。
- 對象的 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è)計方式。
參考出處: