JS基礎(chǔ)知識(shí)總結(jié)

原始(Primitive)類型

涉及面試題:原始類型有哪幾種?null 是對(duì)象嘛?

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

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

首先原始類型存儲(chǔ)的都是值,是沒有函數(shù)可以調(diào)用的,比如 undefined.toString()

此時(shí)你肯定會(huì)有疑問,這不對(duì)呀,明明 '1'.toString() 是可以使用的。其實(shí)在這種情況下,'1' 已經(jīng)不是原始類型了,而是被強(qiáng)制轉(zhuǎn)換成了 String 類型也就是對(duì)象類型,所以可以調(diào)用 toString 函數(shù)。以上代碼相當(dāng)于new String('1').toString(),因?yàn)?strong>new String('1') 創(chuàng)建的是一個(gè)對(duì)象,對(duì)象里是存在toString()方法

除了會(huì)在必要的情況下強(qiáng)轉(zhuǎn)類型以外,原始類型還有一些坑。

其中 JS 的 number 類型是浮點(diǎn)類型的,在使用中會(huì)遇到某些 Bug,比如 0.1 + 0.2 !== 0.3。更詳細(xì)的部分會(huì)在之后提到。

https://user-gold-cdn.xitu.io/2018/9/16/165e0b969d7b43c6?imageView2/0/w/1280/h/960/ignore-error/1

image

另外對(duì)于 null 來說,很多人會(huì)認(rèn)為他是個(gè)對(duì)象類型,其實(shí)這是錯(cuò)誤的。雖然 typeof null 會(huì)輸出 object,但是這只是 JS 存在的一個(gè)悠久 Bug。在 JS 的最初版本中使用的是 32 位系統(tǒng),為了性能考慮使用低位存儲(chǔ)變量的類型信息,000 開頭代表是對(duì)象,然而 null 表示為全零,所以將它錯(cuò)誤的判斷為 object 。雖然現(xiàn)在的內(nèi)部類型判斷代碼已經(jīng)改變了,但是對(duì)于這個(gè) Bug 卻是一直流傳下來。

對(duì)象(Object)類型

涉及面試題:對(duì)象類型和原始類型的不同之處?函數(shù)參數(shù)是對(duì)象會(huì)發(fā)生什么問題?

在 JS 中,除了原始類型那么其他的都是對(duì)象類型了。對(duì)象類型和原始類型不同的是,原始類型存儲(chǔ)的是值,對(duì)象類型存儲(chǔ)的是地址(指針)。當(dāng)你創(chuàng)建了一個(gè)對(duì)象類型的時(shí)候,計(jì)算機(jī)會(huì)在內(nèi)存中幫我們開辟一個(gè)空間來存放值,但是我們需要找到這個(gè)空間,這個(gè)空間會(huì)擁有一個(gè)地址(指針)。

const a = []

對(duì)于常量 a 來說,假設(shè)內(nèi)存地址(指針)為 #001,那么在地址 #001 的位置存放了值 [],常量 a 存放了地址(指針) #001,再看以下代碼

const a = []
const b = a
b.push(1)

當(dāng)我們將變量賦值給另外一個(gè)變量時(shí),復(fù)制的是原本變量的地址(指針),也就是說當(dāng)前變量 b 存放的地址(指針)也是 #001,當(dāng)我們進(jìn)行數(shù)據(jù)修改的時(shí)候,就會(huì)修改存放在地址(指針) #001 上的值,也就導(dǎo)致了兩個(gè)變量的值都發(fā)生了改變。

接下來我們來看函數(shù)參數(shù)是對(duì)象的情況


function test(person) {
  person.age = 23
  person = {
    name: 'jwj',
    age: 18
  }

  return person
}
const p1 = {
  name: 'jwj',
  age: 22
}
const p2 = test(p1)
console.log(p1) // -> ?
console.log(p2) // -> ?

對(duì)于以上代碼,你是否能正確的寫出結(jié)果呢?接下來讓我為你解析一番:

  • 首先,函數(shù)傳參是傳遞對(duì)象指針的副本
  • 到函數(shù)內(nèi)部修改參數(shù)的屬性這步,我相信大家都知道,當(dāng)前 p1 的值也被修改了
  • 最后 person 擁有了一個(gè)新的地址(指針),也就和 p1 沒有任何關(guān)系了,導(dǎo)致了最終兩個(gè)變量的值是不相同的。

typeof vs instanceof


涉及面試題:typeof 是否能正確判斷類型?instanceof 能正確判斷對(duì)象的原理是什么?

typeof 對(duì)于原始類型來說,除了 null 都可以顯示正確的類型

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

typeof 對(duì)于對(duì)象來說,除了函數(shù)都會(huì)顯示 object,所以說 typeof 并不能準(zhǔn)確判斷變量到底是什么類型

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

如果我們想判斷一個(gè)對(duì)象的正確類型,這時(shí)候可以考慮使用 instanceof,因?yàn)閮?nèi)部機(jī)制是通過原型鏈來判斷的,之后的內(nèi)容會(huì)介紹如何自己實(shí)現(xiàn)一個(gè) instanceof。

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

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

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

對(duì)于原始類型來說,直接通過 instanceof 來判斷類型是不行的,當(dāng)然我們還是有辦法讓 instanceof 判斷原始類型的

class PrimitiveString {
  static [Symbol.hasInstance](x) {
    return typeof x === 'string'
  }
}
console.log('hello world' instanceof PrimitiveString) // true

你可能不知道 Symbol.hasInstance 是什么東西,其實(shí)就是一個(gè)能讓我們自定義 instanceof 行為的東西,他是內(nèi)置的 Symbol之一,使用instanceof的時(shí)候就會(huì)調(diào)用她,這是一個(gè)唯一值。 以上代碼等同于 typeof 'hello world' === 'string',所以結(jié)果自然是 true.

關(guān)于 Symbol 的補(bǔ)充 -> https://es6.ruanyifeng.com/#docs/symbol

Symbol.hasInstance

對(duì)象的Symbol.hasInstance屬性,指向一個(gè)內(nèi)部方法。當(dāng)其他對(duì)象使用instanceof運(yùn)算符,判斷是否為該對(duì)象的實(shí)例時(shí),會(huì)調(diào)用這個(gè)方法。比如,foo instanceof Foo在語言內(nèi)部,實(shí)際調(diào)用的是FooSymbol.hasInstance。

class MyClass {
  [Symbol.hasInstance](foo) {
    return foo instanceof Array;
  }
}

[1, 2, 3] instanceof new MyClass() // true

上面代碼中,MyClass是一個(gè)類,new MyClass()會(huì)返回一個(gè)實(shí)例。該實(shí)例的Symbol.hasInstance方法,會(huì)在進(jìn)行instanceof運(yùn)算時(shí)自動(dòng)調(diào)用,判斷左側(cè)的運(yùn)算子是否為Array的實(shí)例。

下面是另一個(gè)例子。

class Even {
  static [Symbol.hasInstance](obj) {
    return Number(obj) % 2 === 0;
  }
}

// 等同于
const Even = {
  [Symbol.hasInstance](obj) {
    return Number(obj) % 2 === 0;
  }
};

1 instanceof Even // false
2 instanceof Even // true
12345 instanceof Even // false

類型轉(zhuǎn)換

涉及面試題:該知識(shí)點(diǎn)常在筆試題中見到,熟悉了轉(zhuǎn)換規(guī)則就不懼怕此類題目了。

首先我們要知道,在 JS 中類型轉(zhuǎn)換只有三種情況,分別是:

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

轉(zhuǎn)Boolean

在條件判斷時(shí),除了 undefined, nullfalse, NaN'', 0, -0,其他所有值都轉(zhuǎn)為 true,包括所有對(duì)象。

對(duì)象轉(zhuǎn)原始類型

對(duì)象在轉(zhuǎn)換類型的時(shí)候,會(huì)調(diào)用內(nèi)置的 [[ToPrimitive]] 函數(shù),對(duì)于該函數(shù)來說,算法邏輯一般來說如下:

  • 如果已經(jīng)是原始類型了,那就不需要轉(zhuǎn)換了
  • 調(diào)用 x.valueOf(),如果轉(zhuǎn)換為基礎(chǔ)類型,就返回轉(zhuǎn)換的值,如果沒有則下一步
  • 調(diào)用 x.toString(),如果轉(zhuǎn)換為基礎(chǔ)類型,就返回轉(zhuǎn)換的值
  • 如果都沒有返回原始類型,就會(huì)報(bào)錯(cuò)

當(dāng)然你也可以重寫 Symbol.toPrimitive ,該方法在轉(zhuǎn)原始類型時(shí)調(diào)用優(yōu)先級(jí)最高。

let a = {
  valueOf() {
    return 0
  },
  toString() {
    return '1'
  },
  [Symbol.toPrimitive]() {
    return 2
  }
}
1 + a // => 3

擴(kuò)展,如何實(shí)現(xiàn) console.log(a == 1 && a == 2 && a == 3) 打印為 true

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


const a1 = {
  value: 0
};
a1.valueOf = function () {
  return {};
};

a1.toString = function () {
  return (this.value += 1);
};
console.log(a1 == 1 && a1 == 2 && a1 == 3); // true

有關(guān)類型轉(zhuǎn)換,包括顯示和隱式轉(zhuǎn)換可以說上好大一篇幅。如果想深入了解推薦看你不知道的JS,里面有很詳細(xì)的回答

四則運(yùn)算符

加法運(yùn)算符不同于其他幾個(gè)運(yùn)算符,它有以下幾個(gè)特點(diǎn):

  • 運(yùn)算中其中一方為字符串,那么就會(huì)把另一方也轉(zhuǎn)換為字符串
  • 如果一方不是字符串或者數(shù)字,那么會(huì)將它轉(zhuǎn)換為數(shù)字或者字符串
1 + '1' // '11'
true + true // 2
4 + [1,2,3] // "41,2,3"

如果你對(duì)于答案有疑問的話,請(qǐng)看解析:

  • 對(duì)于第一行代碼來說,觸發(fā)特點(diǎn)一,所以將數(shù)字 1 轉(zhuǎn)換為字符串,得到結(jié)果 '11'
  • 對(duì)于第二行代碼來說,觸發(fā)特點(diǎn)二,所以將 true 轉(zhuǎn)為數(shù)字 1
  • 對(duì)于第三行代碼來說,觸發(fā)特點(diǎn)二,所以將數(shù)組通過 toString 轉(zhuǎn)為字符串 1,2,3,得到結(jié)果 41,2,3

另外對(duì)于加法還需要注意這個(gè)表達(dá)式 'a' + + 'b'

'a' + + 'b' // -> "aNaN"

因?yàn)?+ 'b' 等于 NaN,所以結(jié)果為 "aNaN",你可能也會(huì)在一些代碼中看到過 + '1' 的形式來快速獲取 number 類型。

那么對(duì)于除了加法的運(yùn)算符來說,只要其中一方是數(shù)字,那么另一方就會(huì)被轉(zhuǎn)為數(shù)字

4 * '3' // 12
4 * [] // 0
4 * [1, 2] // NaN

比較運(yùn)算符

  1. 如果是對(duì)象,就通過 toPrimitive 轉(zhuǎn)換對(duì)象
  2. 如果是字符串,就通過 unicode 字符索引來比較
let a = {
  valueOf() {
    return 0
  },
  toString() {
    return '1'
  }
}
a > -1 // true

在以上代碼中,因?yàn)?a 是對(duì)象,所以會(huì)通過 valueOf 轉(zhuǎn)換為原始類型再比較值。

this

如何正確判斷 this?箭頭函數(shù)的 this 是什么?

this 是很多人會(huì)混淆的概念,但是其實(shí)它一點(diǎn)都不難,只是網(wǎng)上很多文章把簡單的東西說復(fù)雜了。在這里中會(huì)盡量說明白 this 的概念。

我們先來看幾個(gè)函數(shù)調(diào)用的場景

function foo() {
  console.log(this.a)
}
var a = 1
foo()

const obj = {
  a: 2,
  foo: foo
}
obj.foo()

const c = new foo()

接下來我們一個(gè)個(gè)分析上面幾個(gè)場景

  • 對(duì)于直接調(diào)用 foo 來說,不管 foo 函數(shù)被放在了什么地方,this 一定是 window
  • 對(duì)于 obj.foo() 來說,我們只需要記住,誰調(diào)用了函數(shù),誰就是 this,所以在這個(gè)場景下 foo 函數(shù)中的 this 就是 obj 對(duì)象
  • 對(duì)于 new 的方式來說,this 被永遠(yuǎn)綁定在了 c 上面,不會(huì)被任何方式改變 this
    說完了以上幾種情況,其實(shí)很多代碼中的 this 應(yīng)該就沒什么問題了,下面讓我們看看箭頭函數(shù)中的 this
function a() {
  return () => {
    return () => {
      console.log(this)
    }
  }
}
console.log(a()()())

首先箭頭函數(shù)其實(shí)是沒有 this 的,箭頭函數(shù)中的 this 只取決包裹箭頭函數(shù)的第一個(gè)普通函數(shù)的 this。在這個(gè)例子中,因?yàn)榘^函數(shù)的第一個(gè)普通函數(shù)是 a,所以此時(shí)的 thiswindow。另外對(duì)箭頭函數(shù)使用 bind 這類函數(shù)是無效的。

最后一種情況也就是 bind 這些改變上下文的 API 了,對(duì)于這些函數(shù)來說,this 取決于第一個(gè)參數(shù),如果第一個(gè)參數(shù)為空,那么就是 window

說到 bind,不知道大家是否考慮過,如果對(duì)一個(gè)函數(shù)進(jìn)行多次 bind,那么上下文會(huì)是什么呢?

let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)() // => ?

你可能會(huì)認(rèn)為輸出結(jié)果是 a,我們把上述代碼轉(zhuǎn)換成另一種形式

// fn.bind().bind(a) 等于
let fn2 = function fn1() {
  return function() {
    return fn.apply()
  }.apply(a)
}
fn2()

可以從上述代碼中發(fā)現(xiàn),不管我們給函數(shù) bind 幾次,fn 中的 this 永遠(yuǎn)由第一次 bind 決定,所以結(jié)果永遠(yuǎn)是 window。

let a = { name: 'jwj' }
function foo() {
  console.log(this.name)
}
foo.bind(a)() // => 'jwj'

以上就是 this 的規(guī)則了,但是可能會(huì)發(fā)生多個(gè)規(guī)則同時(shí)出現(xiàn)的情況,這時(shí)候不同的規(guī)則之間會(huì)根據(jù)優(yōu)先級(jí)最高的來決定 this 最終指向哪里。

首先,new 的方式優(yōu)先級(jí)最高,接下來是 bind 這些函數(shù),然后是 obj.foo() 這種調(diào)用方式,最后是 foo 這種調(diào)用方式,同時(shí),箭頭函數(shù)的 this 一旦被綁定,就不會(huì)再被任何方式所改變。

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

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