JavaScript基礎原理

[TOC]es6

1. 七種內置類型

基本類型: null,undefined,boolean,number(浮點類型),string,symbol(es6)。
對象:Object。
複製代碼

類型轉換

  • typeof:
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof b // b 沒有聲明,可是還會顯示 undefined
typeof []  // 'object'
typeof {}  // 'object typeof null // 'object' typeof console.log // 'function' 複製代碼
  • valueOf面試

    對象在轉換基本類型時,首先會調用 valueOf 而後調用 toString。而且這兩個方法你是能夠重寫的。瀏覽器

let a = {
    valueOf() {
    	return 0
    toString() {
    return '1';
  },
// Symbol.toPrimitive ,該方法在轉基本類型時調用優先級最高。
  [Symbol.toPrimitive]() {
    return 2;
  }
}
1 + a // => 3
'1' + a // => '12'
複製代碼
  • 比較運算符
若是是對象,就經過 toPrimitive 轉換對象
若是是字符串,就經過 unicode 字符索引來比較
複製代碼

四則運算

只有當加法運算時,其中一方是字符串類型,就會把另外一個也轉爲字符串類型。
其餘運算只要其中一方是數字,那麼另外一方就轉爲數字。
而且加法運算會觸發三種類型轉換:將值轉換爲原始值,轉換爲數字,轉換爲字符串。
複製代碼
1 + '1' // '11'
2 * '2' // 4
[1, 2] + [2, 1] // '1,22,1'
// [1, 2].toString() -> '1,2'
// [2, 1].toString() -> '2,1'
// '1,2' + '2,1' = '1,22,1'

// 對於加號須要注意這個表達式 'a' + + 'b'
'a' + + 'b' // -> "aNaN"
// 由於 + 'b' -> NaN
複製代碼

冷知識

  • NaN 屬於 number 類型,而且 NaN 不等於自身。
  • undefined 不是保留字,可以在低版本瀏覽器被賦值 let undefined = 1

2. 實例對象

new

  • 在調用 new 的過程當中會發生以上四件事情
// 新生成了一個對象
// 連接到原型
// 綁定 this
// 返回新對象

function new() {
    // 建立一個空的對象
    let obj = new Object()
    // 得到構造函數
    let Con = [].shift.call(arguments)
    // 連接到原型
    obj.__proto__ = Con.prototype
    // 綁定 this,執行構造函數
    let result = Con.apply(obj, arguments)
    // 確保 new 出來的是個對象
    return typeof result === 'object' ? result : obj
}
複製代碼
  • 執行優先級
function Foo() {
    return this;
}
Foo.getName = function () {
    console.log('1');
};
Foo.prototype.getName = function () {
    console.log('2');
};

new Foo.getName();   // -> 1
new Foo().getName(); // -> 2

// new Foo() 的優先級大於 new Foo
複製代碼
new (Foo.getName());
(new Foo()).getName();

// 對於第一個函數來講,先執行了 Foo.getName() ,因此結果爲 1;
// 對於後者來講,先執行 new Foo() 產生了一個實例,
// 而後經過原型鏈找到了 Foo 上的 getName 函數,因此結果爲 2。
複製代碼

this

  • 通用規則 new有最高優先級,利用 call,apply,bind 改變 this,優先級僅次於 new。
function foo() {
	console.log(this.a)
}
var a = 1
foo()

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

// 以上二者狀況 `this` 只依賴於調用函數前的對象,優先級是第二個狀況大於第一個狀況

// 如下狀況是優先級最高的,`this` 只會綁定在 `c` 上,不會被任何方式修改 `this` 指向
var c = new foo()
c.a = 3
console.log(c.a)

// 還有種就是利用 call,apply,bind 改變 this,這個優先級僅次於 new
複製代碼
  • 箭頭函數實際上是沒有 this 的,這個函數中的 this 只取決於他外面的第一個不是箭頭函數的函數的 this。在這個例子中,由於調用 a 符合前面代碼中的第一個狀況,因此 this 是 window。而且 this 一旦綁定了上下文,就不會被任何代碼改變。

冷知識

  • instanceof 能夠正確的判斷對象的類型,由於內部機制是經過判斷對象的原型鏈中是否是能找到類型的 prototype。

3. 執行上下文

  1. 全局執行上下文
  2. 函數執行上下文
  3. eval 執行上下文

屬性 VO & AO

變量對象 (縮寫爲VO)就是與執行上下文相關的對象,它存儲下列內容:bash

  1. 變量 (var, VariableDeclaration);
  2. 函數聲明 (FunctionDeclaration, 縮寫爲FD);
  3. 函數的形參
  • 只有全局上下文的變量對象容許經過VO的屬性名稱間接訪問(由於在全局上下文裏,全局對象自身就是一個VO(稍後會詳細介紹)。在其它上下文中是不可能直接訪問到VO的,由於變量對象徹底是實現機制內部的事情。當咱們聲明一個變量或一個函數的時候,同時還用變量的名稱和值,在VO裏建立了一個新的屬性。

激活對象是函數上下文裏的激活對象AO中的內部對象,它包括下列屬性:閉包

  1. callee — 指向當前函數的引用;
  2. length —真正傳遞的參數的個數;
  3. properties-indexes(字符串類型的整數)
  • 屬性的值就是函數的參數值(按參數列表從左到右排列)。 properties-indexes內部元素的個數等於arguments.length. properties-indexes 的值和實際傳遞進來的參數之間是共享的。(譯者注:共享與不共享的區別能夠對比理解爲引用傳遞與值傳遞的區別)

屬性 this&做用域鏈

b() // call b
console.log(a) // undefined

var a = 'Hello world'

function b() {
	console.log('call b')
}
複製代碼
  • 以上衆所周知由於函數和變量提高的緣由。一般提高的解釋是說將聲明的代碼移動到了頂部。可是更準確的解釋應該是:在生成執行上下文時,會有兩個階段。第一個階段是建立的階段(具體步驟是建立 VO),JS解釋器會找出須要提高的變量和函數,而且給他們提早在內存中開闢好空間,函數的話會將整個函數存入內存中,變量只聲明而且賦值爲 undefined,因此在第二個階段,也就是代碼執行階段,咱們能夠直接提早使用。app

  • 在提高的過程當中,相同的函數會覆蓋上一個函數,而且函數優先於變量提高異步

b() // call b second

function b() {
	console.log('call b fist')
}
function b() {
	console.log('call b second')
}
var b = 'Hello world'
複製代碼
  • 對於非匿名的當即執行函數須要注意如下一點
var foo = 1
(function foo() {
    foo = 10
    console.log(foo)
}()) // -> ƒ foo() { foo = 10 ; console.log(foo) }
// 內部獨立做用域,不會影響外部的值
複製代碼

一個面試題

循環中使用閉包解決 var 定義函數的問題函數

for ( var i=1; i<=5; i++) {
	setTimeout( function timer() {
		console.log( i );
	}, i*1000 );
}
// 由於 setTimeout 是個異步函數,全部會先把循環所有執行完畢,這時候 i 就是 6 了,因此會輸出一堆 6。
複製代碼

解決辦法post

第一種使用閉包學習

for (var i = 1; i <= 5; i++) {
  (function(j) {
    setTimeout(function timer() {
      console.log(j);
    }, j * 1000);
  })(i);
}
複製代碼

第二種就是使用 setTimeout 的第三個參數

for ( var i=1; i<=5; i++) {
	setTimeout( function timer(j) {
		console.log( j );
	}, i*1000, i);
}
// 第三個參數及之後的參數均可以做爲func函數的參數,例:
function a(x, y) {
    console.log(x, y) // 2 3
}
setTimeout(a, 1000, 2, 3)
複製代碼

第三種就是使用 let 定義 i 了

for ( let i=1; i<=5; i++) {
	setTimeout( function timer() {
		console.log( i );
	}, i*1000 );
}
複製代碼

由於對於 let 來講,他會建立一個塊級做用域,至關於

{ // 造成塊級做用域
  let i = 0
  {
    let ii = i
    setTimeout( function timer() {
        console.log( i );
    }, i*1000 );
  }
  i++
  {
    let ii = i
  }
  i++
  {
    let ii = i
  }
  ...
}
複製代碼

4. 深淺拷貝

淺拷貝

  • 經過 Object.assign
let a = {
    age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
複製代碼
  • 經過 展開運算符(…)
let a = {
    age: 1
}
let b = {...a}
a.age = 2
console.log(b.age) // 1
複製代碼
  • 弊端:淺拷貝只解決了第一層的問題。若是接下去的值中還有對象的話,那麼就又回到剛開始的話題了,二者享有相同的引用。要解決這個問題,咱們須要引入深拷貝。

深拷貝

  • 經過 JSON.parse(JSON.stringify(object))
let a = {
    age: 1,
    jobs: {
        first: 'FE'
    }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
複製代碼

該方法也是有侷限性的:會忽略 undefined,忽略函數,不能解決循環引用的對象

let obj = {
  a: 1,
  b: {
    c: 2,
    d: 3,
  },
}
obj.c = obj.b
obj.e = obj.a
obj.b.c = obj.c
obj.b.d = obj.b
obj.b.e = obj.b.c
let newObj = JSON.parse(JSON.stringify(obj)) // 會報錯
console.log(newObj)
複製代碼
  • 若是你的數據中含有以上三種狀況下,經過 lodash 的深拷貝函數,或者使用 MessageChannel
function structuralClone(obj) {
  return new Promise(resolve => {
    const {port1, port2} = new MessageChannel();
    port2.onmessage = ev => resolve(ev.data);
    port1.postMessage(obj);
  });
}

var obj = {a: 1, b: {
    c: b
}}
// 注意該方法是異步的
// 能夠處理 undefined 和循環引用對象
const clone = await structuralClone(obj);
複製代碼
文章爲學習筆記,整理自面譜InterviewMap。複製代碼
相關文章
相關標籤/搜索