《你不知道的JS上》筆記

wupengyu發表於2018-09-06

JS是編譯型語言

編譯發生在程式碼執行前幾微秒,簡單來說就是js在執行前要進行編譯,編譯過程發生在程式碼執行前幾微妙,甚至更短。

編譯的步驟

  1. 詞法分析 以var a = 2 為例,詞法分析會將其分成三個有意義的程式碼塊即詞法單元。
  2. 語法分析 將詞法單元組合生成代表了程式語法的結構的樹,即抽象語法書(AST)。
  3. 程式碼生成 將AST生成可執行的程式碼。即將AST轉化成一組機器指令。​​​

LHS \ RHS

如果查詢的目的是對變數進行賦值,那麼就會使用 LHS 查詢;如果目的是獲取變數的值,就會使用 RHS 查詢。

詞法作用域

決定於你在寫程式碼時的塊級作用域

優化

依賴於詞法的靜態分析

eval \ with 會建立新的作用域

在詞法分析階段,無法知道eval \ with會對作用域做怎樣的修改,此時引擎不再對作用域進行任何優化 ##函式作用域

函式宣告 \ 函式表示式

區分函式宣告和表示式最簡單的方法是看 function 關鍵字出現在宣告中的位 置(不僅僅是一行程式碼,而是整個宣告中的位置)。如果 function 是宣告中 的第一個詞,那麼就是一個函式宣告,否則就是一個函式表示式。

let

  1. 隱式的生成塊級作用域
  2. 不存在變數提升

提升

原因

變數(包括函式在內)的所有宣告都會優先執行,只有宣告本身會提升,而賦值或其他執行邏輯會留在原位置

過程

這意味著無論作用域中的宣告出現在什麼地方,都將在程式碼本身被執行前首先進行處理。 可以將這個過程形象地想象成所有的宣告(變數和函式)都會被“移動”到各自作用域的 最頂端,這個過程被稱為提升。 宣告本身會被提升,而包括函式表示式的賦值在內的賦值操作並不會提升。

閉包

定義

當函式能夠記住或訪問所在的詞法作用域,及時是被作用域外呼叫,就產生了閉包

模組

  1. 現代模組機制
  2. 未來的模組機制

關於this

繫結時間點

是在函式執行時繫結的,而非定義時。它的上下文取決於函式呼叫時的各種條件,和在哪裡定義的沒有關係,只取決於函式的呼叫方式。

繫結過程

當函式被呼叫時,會建立一個執行上下文,在這個上下文裡包含了函式在哪裡沒呼叫(呼叫棧),呼叫函式的方法,引數等。this作為執行上下文的一個屬性,可以在函式執行的過程中用到。

繫結型別

  1. 預設繫結 即繫結到全域性,嚴格模式下回繫結到undefined。
    function foo() {
      console.log( this.a );
    }
    var a = 2;
    (function(){
      "use strict";
       foo(); // 2
    })()
    複製程式碼
  2. 隱式繫結 即繫結到最頂層(或最近呼叫物件)上
    function fun() {
      console.log(this.a)
    }
    var obj2 = {
      a: 3,
     fun: fun,
    }
    var obj1 = {
      a: 2,
      obj2: obj2,
    }
    obj1.obj2.fun() // 3
    複製程式碼
  3. 顯式繫結 即用call或apply手動進行繫結
  4. bind方法實現
  5. new繫結(建構函式)
    1. 不存在 其實在js中不存在建構函式,我們所說的建構函式其實就是普通的函式,它只是用new被“構造呼叫”而已。
    2. new發生了什麼?
      1. 建立(或者說構造)一個全新的物件。
      2. 這個新物件會被執行[[原型]]連線。
      3. 這個新物件會繫結到函式呼叫的this。
      4. 如果函式沒有返回其他物件,那麼new表示式中的函式呼叫會自動返回這個新物件。
  6. 箭頭函式 =>

物件

內建物件

基本型別在需要的時候(比如說獲取長度),會被引擎預設轉成內建物件,從而進行方法的呼叫。 基礎型別並不是繼承自內建物件​

    var strPrimitive = "I am a string";
    typeof strPrimitive; // "string"
    strPrimitive instanceof String; // false
    var strObject = new String( "I am a string" );
    typeof strObject; // "object"
    strObject instanceof String; // true
    Object.prototype.toString.call( strObject ); // [object String] 
複製程式碼

null

typeof null === Object; 
複製程式碼

原理是這樣的,不同的物件在底層都表示為二進位制,在 JavaScript 中二進位制前三位都為 0 的話會被判 斷為 object 型別,null 的二進位制表示是全 0,自然前三位也是 0,所以執行 typeof 時會返回“object”

拷貝

  1. 淺拷貝 Object.assign({}, obj)
  2. 深拷貝 JSON.stringify

屬性描述符

getOwnPropertyDescriptor(myObj, 'a')
defineProperty
Object.defineProperty(myObj, 'a', {
  value: 2,			
  ​writable: true,
  configurable: true, 
  enumerable: true 
​})
複製程式碼

Getter 、Setter

var obj = {
  get a() {
    return this._a_
  },
  set a(val) {
   this._a_ = val * 5
  }
}
obj.a = 10
console.log(obj.a) // 50
​
var obj2 = {}
Object.defineProperty(obj2, 'a', {
  get: function() {
    return this._a_
  },
  set: function(val) {
    this._a_ = val * 2
  }
})
obj2.a = 15
console.log(obj2.a) // 30
複製程式碼

存在性

  1. in 'a' in obj1 會檢查obj及其原型鏈上是否有'a'
  2. hasOwnProperty 不會檢查原型鏈,如果需要可以Object.prototype.hasOwnProperty.call(myObj, 'a')

原型(prototype)

constructor

返回例項物件O的建構函式(的引用)。任何一個prototype物件都有一個constructor屬性,指向它的建構函式,每一個例項也有一個constructor屬性,預設呼叫prototype物件的constructor屬性​ 例如

function Test() {
  this.name = 'test'
}
var test = new Test()
console.log(test.constructor === Test) // true
複製程式碼

類constructor

建構函式 constructor 是用於建立和初始化類中建立的一個物件的一種特殊方法.

class Polygon {
    constructor() {
        this.name = "Polygon";
    }
}
class Square extends Polygon {
    constructor() {
        super();
    }
}
class Rectangle {}
Object.setPrototypeOf(Square.prototype, Rectangle.prototype);
console.log(Object.getPrototypeOf(Square.prototype) === Polygon.prototype); //false
console.log(Object.getPrototypeOf(Square.prototype) === Rectangle.prototype); //true
let newInstance = new Square();
console.log(newInstance.name); //Polygon​
複製程式碼

proto

例項物件__proto__指向生成改物件的建構函式的原型 例如

|function Test() {
  this.name = 'test'
}
Test.prototype = {
  color: 'red'
}
var test = new Test()
console.log(test.__proto__ === Test.prototype) // true
console.log(test.__proto__)
複製程式碼

Object.create

var foo = {
something: function() {
  console.log( "Tell me something good..." );
}
};
var bar = Object.create( foo ); 
bar.something(); // Tell me something good...
Object.create(..) 會建立一個新物件(bar)並把它關聯到我們指定的物件(foo)
複製程式碼

這樣 我們就可以充分發揮 [[Prototype]] 機制的威力(委託)並且避免不必要的麻煩 (比如使 用 new 的建構函式呼叫會生成 .prototype 和 .constructor 引用)。 ​

繼承

原型繼承

缺點 例項的屬性都會指向同一個引用 實現

function Parent() {
  this.names = [1, 2, 3]
}
function Child() {
  
}
Child.prototype = new Parent()
var child1 = new Child()
var child2 = new Child()
child1.names.push(4)
console.log(child1.names) // [1,2, 3,4]
console.log(child2.names) // [1,2, 3,4]
複製程式碼

####借用建構函式 實現

function Parent() {
  this.names = [1, 2, 3]
  this.getName = function () {
    console.log(this.name)
  }
}
function Child() {
  Parent.call(this)
}
var child1 = new Child()
var child2 = new Child()
child1.names.push(4)
console.log(child1.names)
console.log(child2.names)
複製程式碼

缺點 每個子例項都會例項化方法一次,記憶體爆炸

組合繼承(最常用)

實現

function Parent() {
  this.names = [1, 2, 3]
}
Parent.prototype.getName = function () {
  console.log(this.names)
}
function Child() {
  Parent.call(this)
}
Child.prototype = new Parent()
var child1 = new Child()
var child2 = new Child()
child1.names.push(4)
child1.getName()
child2.getName()
複製程式碼

缺點

  1. 子類例項上有一份父類的屬性,二者重複造成記憶體浪費
  2. 父類的建構函式被呼叫了兩次​

寄生式組合繼承

實現

function Parent() {
  this.names = [1, 2, 3]
}
Parent.prototype.getName = function () {
  console.log(this.names)
}
function Child() {
  Parent.call(this)
}
Child.prototype = Object.create(Parent.prototype)
var child1 = new Child()
var child2 = new Child()
child1.names.push(4)
child1.getName()
child2.getName()
複製程式碼

優點 屬性不會再原型鏈上重複

行為委託

js中的繼承其實就是在物件間建立關聯關係,而行為委託就是建立這種關聯關係的紐帶。

("原型")物件導向風格

function Foo(who) {
  this.me = who;
}
Foo.prototype.identify = function () {
  return "I am" + this.me;
};
function Bar(who) {
  Foo.call(this,who);
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.speak = function () {
  alert("Hello," + this.identify() + '.');
};
var b1 = new Bar("b1");
var b2 = new Bar("b2");
b1.speak();
b2.speak();
複製程式碼

物件關聯風格

Foo = {
  init:function (who) {
    this.me = who;
  },
  identify:function () {
    return "I am" + this.name
  }
};
Bar = Object.create(Foo);
Bar.speak = function () {
  alert("hello," + this.identify() + '.');
};
var b3 = Object.create(Bar);
b3.init("b3");
var b4 = Object.create(Bar);
b4.init("b4");
b1.speak();
b2.speak();
複製程式碼

相關文章