JS是編譯型語言
編譯發生在程式碼執行前幾微秒,簡單來說就是js在執行前要進行編譯,編譯過程發生在程式碼執行前幾微妙,甚至更短。
編譯的步驟
- 詞法分析 以var a = 2 為例,詞法分析會將其分成三個有意義的程式碼塊即詞法單元。
- 語法分析 將詞法單元組合生成代表了程式語法的結構的樹,即抽象語法書(AST)。
- 程式碼生成 將AST生成可執行的程式碼。即將AST轉化成一組機器指令。
LHS \ RHS
如果查詢的目的是對變數進行賦值,那麼就會使用 LHS 查詢;如果目的是獲取變數的值,就會使用 RHS 查詢。
詞法作用域
決定於你在寫程式碼時的塊級作用域
優化
依賴於詞法的靜態分析
eval \ with 會建立新的作用域
在詞法分析階段,無法知道eval \ with會對作用域做怎樣的修改,此時引擎不再對作用域進行任何優化 ##函式作用域
函式宣告 \ 函式表示式
區分函式宣告和表示式最簡單的方法是看 function 關鍵字出現在宣告中的位 置(不僅僅是一行程式碼,而是整個宣告中的位置)。如果 function 是宣告中 的第一個詞,那麼就是一個函式宣告,否則就是一個函式表示式。
let
- 隱式的生成塊級作用域
- 不存在變數提升
提升
原因
變數(包括函式在內)的所有宣告都會優先執行,只有宣告本身會提升,而賦值或其他執行邏輯會留在原位置
過程
這意味著無論作用域中的宣告出現在什麼地方,都將在程式碼本身被執行前首先進行處理。 可以將這個過程形象地想象成所有的宣告(變數和函式)都會被“移動”到各自作用域的 最頂端,這個過程被稱為提升。 宣告本身會被提升,而包括函式表示式的賦值在內的賦值操作並不會提升。
閉包
定義
當函式能夠記住或訪問所在的詞法作用域,及時是被作用域外呼叫,就產生了閉包
模組
- 現代模組機制
- 未來的模組機制
關於this
繫結時間點
是在函式執行時繫結的,而非定義時。它的上下文取決於函式呼叫時的各種條件,和在哪裡定義的沒有關係,只取決於函式的呼叫方式。
繫結過程
當函式被呼叫時,會建立一個執行上下文,在這個上下文裡包含了函式在哪裡沒呼叫(呼叫棧),呼叫函式的方法,引數等。this作為執行上下文的一個屬性,可以在函式執行的過程中用到。
繫結型別
- 預設繫結
即繫結到全域性,嚴格模式下回繫結到undefined。
function foo() { console.log( this.a ); } var a = 2; (function(){ "use strict"; foo(); // 2 })() 複製程式碼
- 隱式繫結
即繫結到最頂層(或最近呼叫物件)上
function fun() { console.log(this.a) } var obj2 = { a: 3, fun: fun, } var obj1 = { a: 2, obj2: obj2, } obj1.obj2.fun() // 3 複製程式碼
- 顯式繫結 即用call或apply手動進行繫結
- bind方法實現
- new繫結(建構函式)
- 不存在 其實在js中不存在建構函式,我們所說的建構函式其實就是普通的函式,它只是用new被“構造呼叫”而已。
- new發生了什麼?
- 建立(或者說構造)一個全新的物件。
- 這個新物件會被執行[[原型]]連線。
- 這個新物件會繫結到函式呼叫的this。
- 如果函式沒有返回其他物件,那麼new表示式中的函式呼叫會自動返回這個新物件。
- 箭頭函式 =>
物件
內建物件
基本型別在需要的時候(比如說獲取長度),會被引擎預設轉成內建物件,從而進行方法的呼叫。 基礎型別並不是繼承自內建物件
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”
拷貝
- 淺拷貝 Object.assign({}, obj)
- 深拷貝 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
複製程式碼
存在性
- in 'a' in obj1 會檢查obj及其原型鏈上是否有'a'
- 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()
複製程式碼
缺點
- 子類例項上有一份父類的屬性,二者重複造成記憶體浪費
- 父類的建構函式被呼叫了兩次
寄生式組合繼承
實現
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();
複製程式碼