JavaScript這門語(yǔ)言總是能帶給我驚喜,在敲代碼的時(shí)候習(xí)以為常的寫法,退一步再看看發(fā)現(xiàn)自己其實(shí)對(duì)很多基操只有表面的使用,而從來沒思考過為何要這樣操作。
今天整理JS代碼的時(shí)候突然發(fā)出靈魂三連問:
為什么有些時(shí)候操作對(duì)象,可以直接調(diào)用對(duì)象上的方法,但有些時(shí)候我們使用類似Array.from()
的寫法?
在對(duì)象上調(diào)用的方法跟在原型上調(diào)用的方法區(qū)別是什么?這兩者相同么?
為什么JS上可以直接在基礎(chǔ)類型值上調(diào)用對(duì)象上面才存在的方法?基礎(chǔ)類型值上調(diào)用的方法與在對(duì)象上調(diào)用的方法有區(qū)別么?
不同的方法調(diào)用方式
瞟了眼我的代碼,立馬就發(fā)現(xiàn)了一個(gè)調(diào)用類上方法的片段:
const obj = { a: 1 };
console.log(Object.hasOwn(obj, 'a')); // true
// 但是如果在對(duì)象上調(diào)用,則會(huì)拋不存在的錯(cuò)誤
console.log(obj.hasOwn('a')); // TypeError: obj.hasOwn is not a function
在上面的例子里,Object.hasOwn
是一個(gè)可以直接調(diào)用的方法,但令人困惑的是,當(dāng)我們嘗試直接在對(duì)象實(shí)例上調(diào)用hasOwn方法時(shí),卻拋出了一個(gè)類型錯(cuò)誤,是不是有點(diǎn)反直覺? 我仔細(xì)想了一想突然發(fā)現(xiàn),其實(shí)這只是一個(gè)基礎(chǔ)JS概念的一個(gè)外在表現(xiàn),只不過我們習(xí)慣了作為現(xiàn)象使用它,卻很少會(huì)想到它背后的邏輯。
靜態(tài)方法與實(shí)例方法
其實(shí),我們需要做的只是區(qū)分JavaScript靜態(tài)方法和實(shí)例方法。
靜態(tài)方法 是定義在類上的方法,而不是在類的實(shí)例上,靜態(tài)方法內(nèi)部訪問不到this
與實(shí)例變量。所以我們只能通過類來調(diào)用這些方法,而不能通過一個(gè)實(shí)例來調(diào)用
class MyClass {
static staticMethod() {
console.log('這是個(gè)靜態(tài)方法');
}
}
MyClass.staticMethod(); // 正常執(zhí)行
const myInstance = new MyClass();
myInstance.staticMethod(); // Error: myInstance.staticMethod is not a function
實(shí)例方法 是定義在類的原型上的方法,實(shí)例方法內(nèi)可以訪問對(duì)象的屬性,也可以訪問this
,可以直接在實(shí)例化對(duì)象上調(diào)用這些方法
class MyClass {
instanceMethod() {
console.log('這是個(gè)實(shí)例/對(duì)象方法');
}
}
const myInstance = new MyClass();
myInstance.instanceMethod(); // 正常執(zhí)行
概括來說,上面例子中Object.hasOwn()
是一個(gè)需要傳參的、在Object
這個(gè)類上的靜態(tài)方法,所以才需要在類上直接調(diào)用,而不能在實(shí)例對(duì)象上調(diào)用;但在例如arr.sort()
的調(diào)用,實(shí)際調(diào)用的是實(shí)例對(duì)象上的方法
至于為何會(huì)做如此區(qū)分,原因是一個(gè)簡(jiǎn)單的面向?qū)ο缶幊绦枨螅喝绻粋€(gè)方法邏輯不涉及對(duì)象上的屬性,但又邏輯上屬于這個(gè)類,通過接受參數(shù)就可以實(shí)現(xiàn)功能的,則可以作為一個(gè)類的靜態(tài)方法存在。但如果它需要直接訪問類上屬性,直接作為實(shí)例方法顯然更加妥當(dāng)。
原型鏈與方法調(diào)用
JavaScript中的每個(gè)對(duì)象都有一個(gè)原型(prototype)(除了Object.protoype
也就是所有原型的盡頭),對(duì)象的方法實(shí)際上是定義在原型鏈上的。雖然我們可能是在對(duì)象上調(diào)用了一個(gè)方法,實(shí)際上JavaScript引擎會(huì)沿著原型鏈查找該方法并調(diào)用。
const arr = [1, 2, 3];
console.log(arr.join('-')); // "1-2-3"
console.log(Array.prototype.join.call(arr, '-')); // "1-2-3"
上面的例子里,join
方法是數(shù)組的實(shí)例方法。實(shí)例方法可以直接在數(shù)組的實(shí)例上調(diào)用,也可以通過Array.prototype.join.call
的方式來調(diào)用,這倆本質(zhì)上是一樣的。唯一區(qū)別是Array.prototype.join.call
允許我們?cè)谌魏晤愃茢?shù)組的對(duì)象上調(diào)用這個(gè)方法,哪怕它不是一個(gè)真正的數(shù)組。
等等?我們可以在不是數(shù)組的值上調(diào)用join
?是的
const pseudoArray = { 0: 'one', 1: 'two', 2: 'three', length: 3 };
// ❌顯然object上沒有join方法,這樣調(diào)用會(huì)報(bào)錯(cuò)
pseudoArray.join(','); // Error: pseudoArray.join is not a function
// 成功在object上調(diào)用join?。?/span>
const result = Array.prototype.join.call(pseudoArray, ',');
console.log(result); // "one,two,three"
所以,在對(duì)象上調(diào)用實(shí)例方法,等同于按照這個(gè)對(duì)象的原型鏈一層一層向父類上找同名方法來調(diào)用。
基礎(chǔ)類型的自動(dòng)包裝
雖然其他支持面向?qū)ο缶幊谭妒降恼Z(yǔ)言也有類似行為,也就是對(duì)基本類型的自動(dòng)包裝和自動(dòng)拆包,但為了百分百掌握J(rèn)avaScript的行為與他們的異同,還是再來確定一遍吧
每當(dāng)我們?cè)诨绢愋椭瞪希ɡ?code style="box-sizing: inherit; font-family: Consolas, monospace, sans-serif !important; font-size: 0.935em !important; padding: 0px 5px; line-height: 1.8; margin: 0px 3px; display: inline-block; overflow-x: auto; vertical-align: middle; border-radius: 3px !important; background: rgb(247, 247, 249) !important; color: rgb(34, 34, 34) !important; border: none rgb(255, 255, 255) !important; overflow-wrap: break-word !important;">"hello"或6
)上調(diào)用方法,JavaScript引擎都會(huì)先使用基本類型對(duì)應(yīng)的包裝類型對(duì)值進(jìn)行包裝,調(diào)用對(duì)應(yīng)的方法,最后將包裝對(duì)象丟掉還原基礎(chǔ)類型。這是個(gè)引擎內(nèi)部的隱式操作,所以我們沒有任何的感知。
JavaScript對(duì)于以下的基本類型,都有對(duì)應(yīng)的包裝類型??梢酝ㄟ^typeof
操作結(jié)果是基本類型名還是object
來確認(rèn):
string
- String
number
- Number
boolean
- Boolean
symbol
- Object
bigint
- Object
讓我們列一下他們基本類型對(duì)應(yīng)包裝類型的使用:
// string
const primitiveString = "hello";
const objectString = new String("hello");
console.log(typeof primitiveString); // "string"
console.log(typeof objectString); // "object"
// number
const primitiveNumber = 42;
const objectNumber = new Number(42);
console.log(typeof primitiveNumber); // "number"
console.log(typeof objectNumber); // "object"
// boolean
const primitiveBoolean = true;
const objectBoolean = new Boolean(true);
console.log(typeof primitiveBoolean); // "boolean"
console.log(typeof objectBoolean); // "object"
// symbol
const primitiveSymbol = Symbol("description");
const objectSymbol = Object(primitiveSymbol);
console.log(typeof primitiveSymbol); // "symbol"
console.log(typeof objectSymbol); // "object"
// bigint
const primitiveBigInt = 123n;
const objectBigInt = Object(primitiveBigInt);
console.log(typeof primitiveBigInt); // "bigint"
console.log(typeof objectBigInt); // "object"
所以,在基本類型上調(diào)用方法,等同于創(chuàng)建這個(gè)基本類型對(duì)應(yīng)的包裝類型的對(duì)象并調(diào)用方法,最后拆包并返回原始類型的值。本質(zhì)上還是調(diào)用了同類型包裝行為創(chuàng)建的對(duì)象上的方法。
"str".toUpperCase();
// 等同于
(new String("str")).toUpperCase()
// 當(dāng)然,這里巧了,toUpperCase()本來也沒想返回包裝類型的對(duì)象
轉(zhuǎn)自https://www.cnblogs.com/camwang/p/18259567 作者CamWang
該文章在 2024/7/24 16:28:51 編輯過