們日常使用到的邏輯判斷語句有 if...else...、switch...case...、do...while...等。
在簡單場景下,我們可能對這些語法的性能沒有什么感覺,但當遇到復雜的業務場景時,如果處理不善,就會出現大量的邏輯嵌套,可讀性差并且難以擴展。
千里之行始于足下,編寫高可維護性和高質量的代碼,我們就需要從細節處入手,我們今天主要討論 JavaScript 中如何優化邏輯判斷代碼。
function supply(fruit, quantity) {
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
// 條件 1: 水果存在
if (fruit) {
// 條件 2: 屬于紅色水果
if (redFruits.includes(fruit)) {
console.log('紅色水果');
// 條件 3: 水果數量大于 10 個
if (quantity > 10) {
console.log('數量大于 10 個');
}
}
} else {
throw new Error('沒有水果啦!');
}
}
通過上面這個例子,我們可以看到:判斷流程中規中矩,符合現實世界的映射。但是,因代碼層層嵌套,導致閱讀和維護都存在困難。
如果傳入了 fruit 參數,則每次執行都至少需要經過兩步 if 判斷,在性能上也存在問題。
我們來對上面的代碼進行一下優化處理:
function supply(fruit, quantity) {
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
if (!fruit) throw new Error('沒有水果啦'); // 條件 1: 當 fruit 無效時,提前處理錯誤
if (!redFruits.includes(fruit)) return; // 條件 2: 當不是紅色水果時,提前 return
console.log('紅色水果');
// 條件 3: 水果數量大于 10 個
if (quantity > 10) {
console.log('數量大于 10 個');
}
}
這里主要對嵌套層級做了優化,提前終止掉了不符合的條件,將三層嵌套減少到了一層,簡化了代碼結果結構,增強了可閱讀性。
相信我們很多人對下面這種代碼不陌生吧?(想想剛開始寫代碼那會啊)
function pick(color) {
// 根據顏色選擇水果
if (color === 'red') {
return ['apple', 'strawberry'];
} else if (color === 'yellow') {
return ['banana', 'pineapple'];
} else if (color === 'purple') {
return ['grape', 'plum'];
} else {
return [];
}
}
我們需要知道一點原則:if else 更適合于條件區間判斷,而 switch case 更適合于具體枚舉值的分支判斷。
我們使用 switch...case...進行一下改寫:
function pick(color) {
// 根據顏色選擇水果
switch (color) {
case 'red':
return ['apple', 'strawberry'];
case 'yellow':
return ['banana', 'pineapple'];
case 'purple':
return ['grape', 'plum'];
default:
return [];
}
}
switch...case... 優化之后的代碼看上去格式整齊,思路很清晰,但還是很冗長。繼續優化:
const fruitColor = {
red: ['apple', 'strawberry'],
yellow: ['banana', 'pineapple'],
purple: ['grape', 'plum'],
}
function pick(color) {
return fruitColor[color] || [];
}
const fruitColor = new Map()
.set('red', ['apple', 'strawberry'])
.set('yellow', ['banana', 'pineapple'])
.set('purple', ['grape', 'plum']);
function pick(color) {
return fruitColor.get(color) || [];
}
優化之后,代碼更簡潔、更容易擴展。
為了更好的可讀性,還可以通過更加語義化的方式定義對象,然后使用 Array.filter 達到同樣的效果:
const fruits = [
{name: 'apple', color: 'red'},
{name: 'strawberry', color: 'red'},
{name: 'banana', color: 'yellow'},
{name: 'pineapple', color: 'yellow'},
{name: 'grape', color: 'purple'},
{name: 'plum', color: 'purple'}
];
function pick(color) {
return fruits.filter(f => f.color == color);
}
上面使用的例子和手段都比較初級,但是其中的思想卻值得我們細品,希望大家能夠有所收獲!
學習有趣的知識,結識有趣的朋友,塑造有趣的靈魂!
大家好!我是〖編程三昧〗的作者 隱逸王,我的公眾號是『編程三昧』,歡迎關注,希望大家多多指教!
知識與技能并重,內力和外功兼修,理論和實踐兩手都要抓、兩手都要硬!
TML究竟算不算是一門編程語言,這是爭執已久的話題。其實,從本質來講,HTML確實算不上是一門編程語言。
HTML全稱,HyperText Markup Language。字面理解,HTML就是一種超文本語言,何謂超文本,就是其用途和意義已經超越了純文本。因為,HTML重新定義了文本的格式,而且HTML不僅僅只有文本還包括音視頻。
相比其他語言,HTML的字面意義更容易理解,就是為了方便人類和機器閱讀。例如,我們常見的頭標簽,輸入標簽,換行符等等,都是HTML中常見和常用的。此類標簽不僅讓人類能夠輕松理解,而且對于電腦這種機器來說,也是很容理解的。
HTML語言不具備很強的邏輯性。基于此,很多程序員都不承認HTML是一門編程語言。不像其他語言,例如Java,C++,Python等流行語言,這些語言都帶有很強的邏輯和流程控制功能。
不僅僅是HTML無邏輯性和流程控制的問題,同時HTML還是缺乏靈活性的,因為HTML都是按照W3C的標準限定死的語言,主要用于規范HTML文檔的書寫格式。不像其他語言,用戶可以自定義的地方有很多,千變萬化。
HTML不被承認是一門編程語言,最重要的一點是因為,HTML不能按照人類的設計對一件工作進行重復的循環,直至得到讓人類滿意的答案。這一點最重要,其他語言都可以輕松做到。
在編程語言方面的話,小編還是更為推崇c/c++的!雖然Java、Python熱度不斷上漲,但是作為編程界元老的C++依然具有其無可比擬的優勢,小編是一個有著7年工作經驗的架構師,對于c++,自己有做資料的整合,一個完整學習C語言c++的路線,學習資料和工具。可以進我的群7418,18652領取,免費送給大家。希望你也能憑自己的努力,成為下一個優秀的程序員!
當然,我們也不能否認HTML的重要性,作為web領域的重要元老,在當前時候,前端工程師還是有相當大的發展前途的!
在程序員的眼中,HTML是算不上一門編程語言的。雖然如此,但是HTML在WEB領域的重要作用遠遠超越其他任何編程語言,瀏覽器打開i一個網頁第一步就要解析一個HTML的DOM樹,越簡單越重要。
元芳,你怎么看呢?
typeof 操作符返回一個字符串,表示未經計算的操作數的類型。
typeof 運算符后接操作數:
typeof operand
typeof(operand)
operand 一個表示對象或原始值的表達式,其類型將被返回。
下面總結了 typeof 可能的返回值。有關類型和原始值的更多信息,可查看 JavaScript 數據結構 頁面。
類型 | 結果 |
Undefined | "undefined" |
Null | "object" (見下文) |
Boolean | "boolean" |
Number | "number" |
BigInt (ECMAScript 2020 新增) | "bigint" |
String | "string" |
Symbol (ECMAScript 2015 新增) | "symbol" |
宿主對象(由 JS 環境提供) | 取決于具體實現 |
Function 對象 (按照 ECMA-262 規范實現 [[Call]]) | "function" |
其他任何對象 | "object" |
除對象類型(object)以外的其它任何類型定義的不可變的值(值本身無法被改變)。例如(與 C 語言不同),JavaScript 中字符串是不可變的(譯注:如,JavaScript 中對字符串的操作一定返回了一個新字符串,原始字符串并沒有被改變)。我們稱這些類型的值為“原始值”。
1、typeof 總是返回一個字符串。
2、typeof 能正確判斷原始值的類型,null 除外;引用類型數據能正確判斷 Function、函數的類型,其他的都會返回 'object'。
// 數值
console.log(typeof 37 === 'number') // true
console.log(typeof 3.14 === 'number') // true
console.log(typeof (42) === 'number') // true
console.log(typeof Math.LN2 === 'number') // true
console.log(typeof Infinity === 'number') // true
console.log(typeof NaN === 'number') // true 盡管它是 "Not-A-Number" (非數值) 的縮寫
console.log(typeof Number(1) === 'number') // true Number 會嘗試把參數解析成數值
console.log(typeof 42n === 'bigint') // true
// 字符串
console.log(typeof '' === 'string') // true
console.log(typeof 'bla' === 'string') // true
console.log(typeof `template literal` === 'string') // true
console.log(typeof '1' === 'string') // true 注意內容為數字的字符串仍是字符串
console.log(typeof (typeof 1) === 'string') // true typeof 總是返回一個字符串
console.log(typeof String(1) === 'string') // true String 將任意值轉換為字符串,比 toString 更安全
// 布爾值
console.log(typeof true === 'boolean') // true
console.log(typeof false === 'boolean') // true
console.log(typeof Boolean(1) === 'boolean') // Boolean() 會基于參數是真值還是虛值進行轉換
console.log(typeof !!(1) === 'boolean') // true 兩次調用 ! (邏輯非) 操作符相當于 Boolean()
// Symbols
console.log(typeof Symbol() === 'symbol') // true
console.log(typeof Symbol('foo') === 'symbol') // true
console.log(typeof Symbol.iterator === 'symbol') // true
// Undefined
console.log(typeof undefined === 'undefined') // true
console.log(typeof declaredButUndefinedVariable === 'undefined') // true
console.log(typeof undeclaredVariable === 'undefined') // true
// 對象
console.log(typeof { a: 1 } === 'object') // true
// 使用 Array.isArray 或者 Object.prototype.toString.call
// 區分數組和普通對象
console.log(typeof [1, 2, 4] === 'object') // true
console.log(typeof new Date() === 'object') // true
console.log(typeof /regex/ === 'object') // true 歷史結果請參閱正則表達式部分
// 使用 new 操作符
// 除 Function 外的所有構造函數的類型都是 'object'
var func = new Function()
console.log(typeof func) // 返回 'function'
var A = function() {}
var b = new A()
console.log(typeof b) // 返回 'object'
// 下面的例子令人迷惑,非常危險,沒有用處。避免使用它們。
console.log(typeof new Boolean(true) === 'object') // true
console.log(typeof new Number(1) === 'object') // true
console.log(typeof new String('abc') === 'object') // true
// 函數
console.log(typeof function () { } === 'function') // true
console.log(typeof class C { } === 'function') // true
console.log(typeof Math.sin === 'function') // true
// Null
// JavaScript 誕生以來便如此
console.log(typeof null === 'object') // true
在 JavaScript 最初的實現中,JavaScript 中的值是由一個表示類型的標簽和實際數據值表示的。對象的類型標簽是 0。由于 null 代表的是空指針(大多數平臺下值為 0x00),因此,null 的類型標簽是 0,typeof null 也因此返回 "object"。(參考來源)
console.log(typeof 0); // number
console.log(typeof BigInt(Number.MAX_SAFE_INTEGER)); // bigint
console.log(typeof '0'); // string
console.log(typeof true); // boolean
console.log(typeof undefined); // undefined
console.log(typeof function () { }); // function
console.log(typeof Symbol); // function
console.log(typeof Symbol()); // symbol
console.log(typeof Date); // function
console.log(typeof Date()); // string
console.log(typeof new Date); // object
console.log(typeof new Date()); // object
console.log(typeof RegExp); // function
console.log(typeof RegExp()); // object
console.log(typeof new RegExp); // object
console.log(typeof new RegExp()); // object
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof null); // object
如果我們想判斷一個對象的正確類型,可以考慮使用 instanceof,因為內部機制是通過 判斷實例對象的 __proto__ 和生成該實例的構造函數的 prototype 是不是引用的同一個地址(也就是原型鏈的方式)來判斷的。
在 ECMAScript 2015 之前,typeof 總能保證對任何所給的操作數返回一個字符串。即便是沒有聲明的標識符,typeof 也能返回 'undefined'。使用 typeof 永遠不會拋出錯誤。
但在加入了塊級作用域的 let 和 const 之后,在其被聲明之前對塊中的 let 和 const 變量使用 typeof 會拋出一個 ReferenceError。塊作用域變量在塊的頭部處于“暫存死區”,直至其被初始化,在這期間,訪問變量將會引發錯誤。
typeof undeclaredVariable === 'undefined';
typeof newLetVariable; // ReferenceError
typeof newConstVariable; // ReferenceError
typeof newClass; // ReferenceError
let newLetVariable;
const newConstVariable = 'hello';
class newClass{};
instanceof 運算符用于檢測構造函數 prototype 屬性是否出現在某個實例對象的原型鏈上。可以用來判斷都屬于 Object 類型和一些特殊情況的對象,如:數組和對象,但不能用于基礎數據類型。
object instanceof constructor
object 某個實例對象
constructor 某個構造函數
instanceof 運算符用來檢測 constructor.prototype 是否存在于參數 object 的原型鏈上。
B instanceof A:判斷 B 是否為 A 的實例,可以用于繼承關系中
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
const auto = new Car('Honda', 'Accord', 1998);
console.log(auto instanceof Car);
// expected output: true
console.log(auto instanceof Object);
// expected output: true
// 定義構造函數
function C(){}
function D(){}
var o = new C();
o instanceof C; // true,因為 Object.getPrototypeOf(o) === C.prototype
o instanceof D; // false,因為 D.prototype 不在 o 的原型鏈上
o instanceof Object; // true,因為 Object.prototype.isPrototypeOf(o) 返回 true
C.prototype instanceof Object // true,同上
C.prototype = {};
var o2 = new C();
o2 instanceof C; // true
o instanceof C; // false,C.prototype 指向了一個空對象,這個空對象不在 o 的原型鏈上.
D.prototype = new C(); // 繼承
var o3 = new D();
o3 instanceof D; // true
o3 instanceof C; // true 因為 C.prototype 現在在 o3 的原型鏈上
需要注意的是,如果表達式 obj instanceof Foo 返回 true,則并不意味著該表達式會永遠返回 true,因為 Foo.prototype 屬性的值有可能會改變,改變之后的值很有可能不存在于 obj 的原型鏈上,這時原表達式的值就會成為 false。另外一種情況下,原表達式的值也會改變,就是改變對象 obj 的原型鏈的情況,雖然在目前的ES規范中,我們只能讀取對象的原型而不能改變它,但借助于非標準的 __proto__ 偽屬性,是可以實現的。比如執行 obj.__proto__ = {} 之后,obj instanceof Foo 就會返回 false 了。
A 是 B 的父對象,c 是 B 的實例,c instanceof A 與 c instanceof B 結果均為 true。
function A() { }
function B() { }
B.prototype = new A()
const c = new B()
console.log(c instanceof B); // true
console.log(c instanceof A); // true
console.log(A instanceof B); // false
console.log(B instanceof A); // false
console.log(B.__proto__ === A.prototype); // false
console.log(B.prototype.__proto__ === A.prototype); // true
console.log(c instanceof Object); // true c 屬于
console.log(B instanceof Object); // true
console.log(A instanceof Object); // true
下面的代碼使用了 instanceof 來證明:String 和 Date 對象同時也屬于Object 類型(他們是由 Object 類派生出來的)。
但是,使用對象文字符號創建的對象在這里是一個例外:雖然原型未定義,但 instanceof Object 返回 true。
var simpleStr = "This is a simple string";
var myString = new String();
var newStr = new String("String created with constructor");
var myDate = new Date();
var myObj = {};
var myNonObj = Object.create(null);
console.log(simpleStr instanceof String) // 返回 false, 非對象實例,因此返回 false
console.log(myString instanceof String) // 返回 true
console.log(newStr instanceof String) // 返回 true
console.log(myString instanceof Object) // 返回 true
console.log(myObj instanceof Object) // 返回 true, 盡管原型沒有定義
console.log(({}) instanceof Object) // 返回 true, 同上
console.log(myNonObj instanceof Object) // 返回 false, 一種創建非 Object 實例的對象的方法
console.log(myString instanceof Date) // 返回 false
console.log(myDate instanceof Date) // 返回 true
console.log(myDate instanceof Object) // 返回 true
console.log(myDate instanceof String) // 返回 false
toString.call() 或 toString.apply() 方法幾乎可以精準判斷各類數據的類型。
console.log(Object.prototype.toString.call("kevin")) // [object String]
console.log(Object.prototype.toString.call(18)) // [object Number]
console.log(Object.prototype.toString.call(true)) // [object Boolean]
console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
console.log(Object.prototype.toString.call(null)) // [object Null]
console.log(Object.prototype.toString.call(NaN)) // [object Number]
console.log(Object.prototype.toString.call({ name: "kevin" })) // [object Object]
console.log(Object.prototype.toString.call(function () { })) // [object Function]
console.log(Object.prototype.toString.call([])) // [object Array]
console.log(Object.prototype.toString.call(new Date)) // [object Date]
console.log(Object.prototype.toString.call(/\d/)) // [object RegExp]
console.log(Object.prototype.toString.call(Math)) // [object Math]
function Person() { }
console.log(Object.prototype.toString.call(new Person)) // [object Object]
var o = { [Symbol.toStringTag]: "A" }
console.log(Object.prototype.toString.call(o)) // [object A]
console.log(window.toString()) // "[object Window]"
console.log(Object.prototype.toString.call(window)) // "[object Window]"
console.log(Object.prototype.toString.call(Symbol())); // "[object Symbol]"
// 封裝
function getTypeof(data) {
let dataType = Object.prototype.toString.call(data);
return dataType.slice(8, -1)
}
console.log(getTypeof(18)) // Number
console.log(getTypeof("kevin")) // String
console.log(getTypeof(new Date)) // Date
console.log(getTypeof([])) // Array
console.log(getTypeof({})) // Object
function Person() { }
console.log(getTypeof(new Person)) // Object
console.log(getTypeof(new Person())) // Object
obj.toString()
一個表示該對象的字符串
每個對象都有一個 toString() 方法,當該對象被表示為一個文本值時,或者一個對象以預期的字符串方式引用時自動調用。默認情況下,toString() 方法被每個 Object 對象繼承。如果此方法在自定義對象中未被覆蓋,toString() 返回 "[object type]",其中 type 是對象的類型。以下代碼說明了這一點:
var o = new Object();
console.log(o.toString()); // [object Object]
備注:如 ECMAScript 5 和隨后的 Errata 中所定義,從 JavaScript 1.8.5 開始,toString() 調用 null 返回[object Null],undefined 返回 [object Undefined]。請參閱下面的使用toString()檢測對象類型。
覆蓋默認的 toString 方法
可以自定義一個方法,來取代默認的 toString() 方法。該 toString() 方法不能傳入參數,并且必須返回一個字符串。自定義的 toString() 方法可以是任何我們需要的值,但如果它附帶有關對象的信息,它將變得非常有用。
以下代碼定義了 Dog 對象類型,并創建了一個 Dog 類型的 theDog 對象:
function Dog(name,breed,color,sex) {
this.name = name;
this.breed = breed;
this.color = color;
this.sex = sex;
}
var theDog = new Dog("Gabby", "Lab", "chocolate", "female");
// 如果當前的對象調用了 toString() 方法,它將會返回從 Object繼承而來的 toString() 方法的返回默認值:
console.log(theDog.toString()); // 返回 [object Object]
// 下面的代碼中定義了一個叫做 dogToString() 的方法來覆蓋默認的 toString() 方法。
// 這個方法生成一個 "property = value;" 形式的字符串,該字符串包含了當前對象的 name、breed、color 和 sex 的值。
Dog.prototype.toString = function dogToString() {
var ret = "Dog " + this.name + " is a " + this.sex + " " + this.color + " " + this.breed;
return ret;
}
// 也可以這樣寫
Dog.prototype.toString = function dogToString() {
return `Dog ${this.name} is a ${this.sex} ${this.color} ${this.breed}`;
}
// 使用上述代碼,任何時候在字符串上下文中使用 theDog.toString() 時,
// JavaScript 都會自動調用 dogToString() 方法(dogToString() 可以是一個匿名函數),并且返回以下字符串:
// "Dog Gabby is a female chocolate Lab"
console.log(theDog.toString()); // 返回 "Dog Gabby is a female chocolate Lab"
// 也可以這樣寫
Dog.prototype.toString = function dogToString() {
return '[object Dog]';
}
Dog.prototype[Symbol.toStringTag] = 'Dog'
// 也可以這樣寫
theDog[Symbol.toStringTag] = 'Dog'
console.log(theDog.toString()); // 返回 [object Dog]
console.log(Dog.prototype.toString()); // 返回 [object Dog]
console.log(Dog.prototype.toString.call(theDog)); // 返回 [object Dog]
console.log(Object.prototype.toString.call(theDog)); // 返回 [object Object]
可以通過 toString() 來獲取每個對象的類型。為了每個對象都能通過 Object.prototype.toString() 來檢測,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式來調用,傳遞要檢查的對象作為第一個參數,稱為 thisArg。
var toString = Object.prototype.toString;
toString.call(new Date); // [object Date]
toString.call(new String); // [object String]
toString.call(Math); // [object Math]
//Since JavaScript 1.8.5
toString.call(undefined); // [object Undefined]
toString.call(null); // [object Null]
Object.prototype.toString.call(obj) 類型檢測的原理是什么?首先我們來看一下 toString() 方法:
var num = 1
console.log(num.toString()) // '1'
var str = 'kevin'
console.log(str.toString()) // 'kevin'
var bool = false
console.log(bool.toString()) // 'false'
var arr = [1, 2, 3]
console.log(arr.toString()) // '1,2,3'
var obj = { name: 'kevin' }
console.log(obj.toString()) // '[object Object]'
var fn = function(){}
console.log(fn.toString()) // 'function(){}'
console.log(JSON.toString()) // '[object JSON]'
console.log(Atomics.toString()) // '[object Atomics]'
console.log(null.toString()) // Cannot read property 'toString' of null
console.log(undefined.toString() // Cannot read property 'toString' of undefined
console.log(window.toString()) // '[object Window]'
從以上示例可以知道 toString 是將數據轉換為字符串(null 和 undefined 除外),并且各種類型的數據轉換為字符串的方式又不一樣。即若參數不為 null 或 undefined,則將參數轉為對象,再作判斷。對于原始類型,轉為對象的方法即裝箱。
轉為對象后,取得該對象的 [Symbol.toStringTag] 屬性值(可能會遍歷原型鏈)作為 tag,如無該屬性,或該屬性值不為字符串類型,則依下表取得 tag,然后返回 "[object " + tag + "]" 形式的字符串。
新標準引入了 [Symbol.toStringTag] 屬性,是為了把此方法接口化,用于規范新引入的對象對此方法的調用。但對于“老舊”的對象,就只能直接輸出值,以保證兼容性。
// 1. 三個容器對象。這類對象用作命名空間,用于存儲同一類方法。
JSON[Symbol.toStringTag]; // => "JSON"
Math[Symbol.toStringTag]; // => "Math"
Atomics[Symbol.toStringTag]; // => "Atomic"
// 這三個對象的 toString() 都沒有重寫,直接調用 toString() 方法也可以得到相同的結果。
JSON.toString(); // => "[object JSON]"
Math.toString(); // => "[object Math]"
Atomics.toString(); // => "[object Atomics]"
// 2. 兩個新引入的類型 BigInt 和 Symbol。
BigInt.prototype[Symbol.toStringTag]; // => "BigInt"
Symbol.prototype[Symbol.toStringTag]; // => "Symbol"
// 3. 四個集合(Collection)對象。
Set.prototype[Symbol.toStringTag]; // => "Set"
Map.prototype[Symbol.toStringTag]; // => "Map"
WeakSet.prototype[Symbol.toStringTag]; // => "WeakSet"
WeakMap.prototype[Symbol.toStringTag]; // => "WeakMap"
// 4. 在不同的實現中,有些第三方對象也部署了此屬性。
// 比如在瀏覽器中:
Window.prototype[Symbol.toStringTag]; // => "Window"
HTMLElement.prototype[Symbol.toStringTag]; // => "HTMLElement"
Blob.prototype[Symbol.toStringTag]; // => "Blob"
// 5. 模塊命名空間對象(Module Namespace Object)。
// 新引入的模塊命名空間對象(Module Namespace Object)也是部署了此屬性的。
import * as module from "./export.js";
module[Symbol.toStringTag]; // => "Moduel"
// 6. 在 Node.js 中
global[Symbol.toStringTag]; // => "global"
我們再來看一下 Object 以及其原型上的 toString 方法:
Object.toString(); // "function Object() { [native code] }"
Object.prototype.toString(); // "[object Object]"
var o = new Object();
console.log(o.toString()); // 返回 [object Object]
console.log(o.__proto__.toString()); // 返回 [object Object]
console.log(o.__proto__.toString === Object.prototype.toString); // true
我們可以看出 Object 和它的原型鏈上各自有一個 toString 方法,Object 輸出的是其函數體 "function Object() { [native code] }",而 Object 原型上輸出的是其類型 "[object Object]"。
數據類型 | 例子 | 輸出 |
字符串 | "foo".toString() | "foo" |
數字 | 1.toString() | Uncaught SyntaxError: Invalid or unexpected token |
布爾值 | true.toString() | "true" |
undefined | undefined.toString() | Uncaught TypeError: Cannot read property 'toString' of undefined |
null | null.toString() | Uncaught TypeError: Cannot read property 'toString' of null |
String | String.toString() | "function String() {[native code]}" |
Number | Number.toString() | "function Number() {[native code]}" |
Boolean | Boolean.toString() | "function Boolean() {[native code]}" |
Array | Array.toString() | "function Array() {[native code]}" |
Function | Function.toString() | "function Function() {[native code]}" |
Date | Date.toString() | "function Date() {[native code]}" |
RegExp | RegExp.toString() | "function RegExp() {[native code]}" |
Error | Error.toString() | "function Error() {[native code]}" |
Promise | Promise.toString() | "function Promise() {[native code]}" |
Object | Object.toString() | "function Object() {[native code]}" |
Math | Math.toString() | "[object Math]" |
Window | Window.toString() | "function Window() { [native code] }" |
window | window.toString() | "[object Window]" |
數據類型調用 toString() 方法的返回值,由此我們看出不同的數據類型都有其自身toString()方法
// Boolean 類型,tag 為 "Boolean"
console.log(Object.prototype.toString.call(true)); // => "[object Boolean]"
// Number 類型,tag 為 "Number"
console.log(Object.prototype.toString.call(1)); // => "[object Boolean]"
// String 類型,tag 為 "String"
console.log(Object.prototype.toString.call("")); // => "[object String]"
// Array 類型,tag 為 "String"
console.log(Object.prototype.toString.call([])); // => "[object Array]"
// Arguments 類型,tag 為 "Arguments"
console.log(Object.prototype.toString.call((function() {
return arguments;
})())); // => "[object Arguments]"
// Function 類型, tag 為 "Function"
console.log(Object.prototype.toString.call(function(){})); // => "[object Function]"
// Error 類型(包含子類型),tag 為 "Error"
console.log(Object.prototype.toString.call(new Error())); // => "[object Error]"
// RegExp 類型,tag 為 "RegExp"
console.log(Object.prototype.toString.call(/\d+/)); // => "[object RegExp]"
// Date 類型,tag 為 "Date"
console.log(Object.prototype.toString.call(new Date())); // => "[object Date]"
// 其他類型,tag 為 "Object"
console.log(Object.prototype.toString.call(new class {})); // => "[object Object]"
// window 全局對象
console.log(Object.prototype.toString.call(window); // => "[object Window]")
在 JavaScript 中,所有類都繼承于 Object,因此 toString 方法應該也被繼承了,但由上述可見事實并不像我們想的那樣,其實各數據類型使用 toString() 后的結果表現不一的原因在于:所有類在基礎 Object 的時候,改寫了 toString 方法。盡管如此,但 Object 原型上的方法是可以輸出數據類型的,因此我們想判斷數據類型時,也只能使用原型上的 toString 方法:Object.prototype.toString.call(object) 。
toString(); // "[object Undefined]"
(function(){
console.log(toString()); // [object Undefined]
})();
也就是說直接調用toString()方法,等價于
Object.prototype.toString.call(); // "[object Undefined]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
即:直接調用 toString() 方法這里不可以理解成為全局作用域調用 toString() 方法,即 window.toString()
所以直接調用 toString() 應該就是變相的 undefined.toString() 方法(這里說的是相當于,實際 undefined 并沒有方法,調用會報錯)。
// 定義一個數組
var arr = [1, 2, 3]
// 數組原型上是否具有 toString() 方法
console.log(Array.prototype.hasOwnProperty('toString')) //true
// 數組直接使用自身的 toString() 方法
console.log(arr.toString()) // '1,2,3'
// delete操作符刪除數組原型上的 toString()
delete Array.prototype.toString
// 刪除后,數組原型上是否還具有 toString() 方法
console.log(Array.prototype.hasOwnProperty('toString')) //false
// 刪除后的數組再次使用 toString() 時,會向上層訪問這個方法,即 Object 的 toString()
console.log(arr.toString()) // '[object Array]'
當我們把 Array 自身的 toString() 方法刪除之后,再次使用它時,由原型鏈它會向上查找這個方法,即 Object 的 toString(),也便將 Object 上的 toString() 方法作用在數組上,得出其數據類型 [object Array] 。
經常有人用 toString.call/apply(類型) 去代替 Object.prototype.toString.call/apply(類型) 使用,其實這樣是不嚴謹的,容易導致一些問題,如下所示
function toString(){
console.log("1")
}
toString(); // 1
toString.call({}); // 1
toString.call([]); // 1
我們可以發現,當我們自定義了 toString() 方法時,直接調用 toString() 方法,就不會再默認調用 Object 類的 toString() 方法,而是會使用我們自定義的方法,這樣可能得不到我們想要的結果,所以我們還是應當盡量使用 Object.prototype.toString.call/apply(類型)。
正因為 Object.prototype.toString() 本身允許被重寫,像 Array、Boolean、Number 的 toString 就被重寫過,所以需要調用 Object.prototype.toString.call(arg) 或 Object.prototype.toString.apply(arg) 或 Reflect.apply() 來判斷 arg 的類型,call 將 arg 的上下文指向 Object,所以 arg 執行了 Object 的 toString() 方法。
至于 call,就是改變對象的 this 指向,當一個對象想調用另一個對象的方法,可以通過 call 或者 apply 改變其 this 指向,將其 this 指向擁有此方法的對象,就可以調用該方法了。
var x = {
toString() {
return "X";
},
};
x.toString(); // => "X"
Object.prototype.toString.call(x); // => "[object Object]"
Object.prototype.toString.apply(x); // => "[object Object]"
Reflect.apply(Object.prototype.toString, x, []); // => "[object Object]"
*請認真填寫需求信息,我們會在24小時內與您取得聯系。