前言
餘為前端菜鳥,感姿勢水平匱乏,難觀前端之大局。遂決定循前端知識之脈絡,以興趣為引,輔以幾分堅持,望於己能解惑致知、於同道能助力一二,豈不美哉。
本系列程式碼及文件均在 此處
內建型別
分類
- 基本型別
number, string, boolean, null, undefined, symbol
- 物件Object
判斷型別
-
typeof
對於基本型別(null除外)都能得到正常結果
對於物件(函式除外)都顯示object
-
Object.prototype.toString.call
正確獲取物件type的方法
Object.prototype.toString.call([]) // [object Array] 複製程式碼
型別轉換
-
顯示轉換
Number(), Boolean(), String(), Object()
-
隱式轉換
基本型別之間的轉換比較簡單,怪異的地方在於物件轉基本型別
物件轉基本型別發生在一些運算子操作時
-
!
操作符將物件轉boolean
這個是最簡單的,全是true
-
四則運算
+
:一方為字串則另一方也轉為字串[1, 2] + [2, 1] === '1,22,1' 'a' + +'b' ===' aNaN' 複製程式碼
其他:一方為數字則另一方也轉為數字
-
==
最鬧心的東西,其實不需要去硬記那些規則,大致如下:
- 同型別比較不用多說,注意
NaN!==NaN, +0 == -0
- 一方為number或者boolean時,另一方轉為基本型別以number比較
- 一方為string時,另一方轉為基本型別再參照上一條比較
- 同型別比較不用多說,注意
-
物件轉基本型別(toPrimitive)
Symbol.prototype[@@toPrimitive]
,Object.prototype.toString()
,Object.prototype.valueOf()
以上是物件轉基本型別時呼叫的方法,可以自己重寫
-
原型和繼承
原型鏈
-
函式的
prototype
每個函式都有一個原型prototype(Function.prototype.bind()建立的函式除外,因為Function.prototype是內建的東西),函式的prototype初始只有constructor一個屬性,指向函式本身
-
物件的
__proto__
由建構函式生成的物件會有一個__proto__的屬性,稱為隱式原型,指向了建構函式的原型
a.__proto__ === A.prototype 複製程式碼
我們所說的原型鏈其實就是根據這樣的聯絡構建起來的一個鏈路
-
new
關鍵字new
關鍵字執行的時候實際上做了這麼幾步- 建立空物件
- 繫結原型
- 繫結this
- 返回物件
function create(){ const obj = new Object() // Con為建構函式本身 const Con = [].shift.call(arguments) obj.__proto__ = Con.prototype // 繫結this const res = Con.apply(obj, arguments) return res } 複製程式碼
-
instanceof
js的繼承實際上不是說根據類的定義將類的屬性copy到例項上,js的繼承實際上是通過__proto__和prototype建立聯絡,使得方法/屬性可以通過原型鏈訪問到。因此要判斷繼承關係還是需要通過原型鏈
function instanceof(a, b) { const proto = b.prototype let _proto = a.__proto__ while(true) { // 到頭了 if (_proto === null) { return false } if (proto === _proto) { return true } _proto = _proto.__proto__ } } 複製程式碼
繼承
es6的class簡化了繼承的寫法
class A {
constructor(value) {
// 生成物件的例項屬性
this.value = value
}
// doA掛在A的prototype上
doA(){
console.log(123)
}
}
class B extends A{
// 達到重寫的目的,但實際上A的原型上的方法還是在的
doA(){
console.log(456)
}
}
複製程式碼
this,執行上下文與作用域
before it
介紹幾個概念
-
Handle
記憶體分配在堆(heap)內進行,javaScript的值和物件都在堆內,Handle(控制程式碼)是指向物件的一個指標,所有V8物件都是通過控制程式碼訪問,這樣GC機制才能實現。
Handle分為Local和Persistent兩種,前者被HandleScope管理,是區域性的,後者不受HandleScope管理,需要Persistent::New, Persistent::Dispose配對使用,類似C++的new和delete。
-
Handle Scope
HandleScope是Handle的容器,HandleScope生命週期結束時Handle會被釋放,引起heap中物件引用的更新。
-
Context
Context是javaScript執行的環境,其中包含了javaScript的內建物件和內建函式等。Context可以巢狀,可以從A切換到B。
-
看點不一樣的
hello world
#include <v8.h> using namespace v8; int main(int argc, char* argv[]) { // 建立一個Handle容器 HandleScope handle_scope; // 建立一個執行環境/上下文 Persistent<Context> context = Context::New(); // 進入建立的執行環境 Context::Scope context_scope(context); // 建立String Handle<String> source = String::New("'Hello' + ', World!'"); // 編譯原始碼 Handle<Script> script = Script::Compile(source); // 執行得到結果 Handle<Value> result = script->Run(); // 執行環境銷燬 context.Dispose(); // 列印結果 String::AsciiValue ascii(result); printf("%s\n", *ascii); return 0; // handle_scope生命週期結束,handle都被釋放 } 複製程式碼
執行上下文
js執行一段可執行程式碼時會產生執行上下文,執行上下文存在棧中
舉個?
function B() {}
function A() {
B()
}
複製程式碼
like this:
stack = []
stack.push(globalContext) // 全域性Context
stack.push(Acontext) // 執行A函式
stack.push(Bcontext) // 呼叫B函式
stack.pop() // B執行完畢,出棧
stack.pop() // A執行完畢,出棧
複製程式碼
上下文中內容
-
變數物件VO(variable object)
變數物件儲存了上下文中定義的變數和宣告,也包含一些內建的物件。
全域性上下文中的變數物件就是全域性物件,包含有很多內建的物件和方法
函式上下文中的變數物件又稱活動物件AO(active object),初始時只有Arguments物件
-
進入執行上下文: 進行函式、變數宣告和形參宣告
function foo(a) { f() function f() { console.log(123) } function f() { console.log(456) } var f = 1 } foo(1) // 456 複製程式碼
函式優先於變數被提升宣告,同名函式會被覆蓋
與已有函式/變數同名的變數宣告會被忽略
此時的變數物件/活動物件為
AO = { arguments: { 0: 1, length: 1}, a: 1, f: reference to function f() {}, } 複製程式碼
-
執行過程
根據程式碼執行結果修改活動物件的值
-
-
作用域鏈[[scope]]
我們說函式的作用域是在函式定義的時候確定的,那是因為函式都有一個內部屬性[[scope]],包含了父級變數物件
function foo() { function bar() { } } 複製程式碼
此時兩個函式的[[scope]]:
foo.[[scope]] = [globalContext.VO] bar.[[scope]] = [globalContext.VO, fooContext.AO] 複製程式碼
函式執行時,進入對應上下文,會將該上下文中的AO加到[[scope]]的前面
bar() // AO為bar執行上下文中的活躍物件 // Scope為bar執行上下文的作用域鏈 Scope = [AO].concat(bar.[[scope]]) 複製程式碼
而這個Scope就是我們查詢變數值的作用域鏈
舉個?
var a = 'outside' function foo() { var a = 'inside' return a } foo() 複製程式碼
步驟如下
// 1.建立foo函式 foo.[[scope]] = [globalContext.VO] // 2.執行foo函式 stack = [globalContext, fooContext] // 上下文壓棧 // 3.複製函式[[scope]]建立作用域鏈 fooContext.Scope = fooContext.[[scope]] // 4.函式/變數宣告 -> 活動物件建立 AO = { arguments: { length: 0 }, a: undefined } // 5.將AO加到作用域鏈頂端 fooContext.Scope = [AO, fooContext.[[scope]]] // 6.函式執行修改AO值 AO = { arguments: { length: 0 }, a: 'inside' } // 7.函式返回,fooContext出棧 stack = [globalContext] 複製程式碼
-
this
this的指向規則其實很簡單,分為以下幾種情況:
- 全域性/普通呼叫 -----> 頂層物件/undefined(嚴格模式)
- 存在引用,即函式作為物件屬性被直接呼叫 -----> 該物件
- 建構函式生成 -----> 生成物件
call, apply. bind
強制改變this指向
然而...this賦值的原理,並不是很懂啊...很難受
小結
函式建立時生成詞法作用域[[scope]],執行時建立執行上下文並壓棧,執行上下文建立時需要複製函式的作用域建立作用域鏈。
然後進行函式/變數的宣告和形參宣告(根據arguments物件生成),根據上述物件生成活動物件,並將活動物件加到作用域鏈頂端,變數查詢依賴該作用域鏈。
函式執行時會修改AO的值,會根據呼叫方式為this賦值,函式執行完畢後,上下文出棧(暫不考慮閉包)
閉包Closure
概念
記得7788的一句話:
A closure is a function plus the connection to the variables of its surrounding scopes.
閉包是由函式和函式建立時的詞法環境組成的,可以訪問到建立時能訪問到的所有區域性變數。
function a () {
const c = 1
return function b () {
console.log(c)
}
}
const d = a()
d()
複製程式碼
這裡存在一個閉包,函式b在建立環境之外呼叫時依然能夠訪問到建立時環境的區域性變數。
回到上一小節,閉包的情況下,因為內部函式存在對外部函式的變數引用,外部函式的執行上下文並不會出棧,導致記憶體佔用會增加,所以閉包其實並不是那麼美好。
注意
js內可以說是無處不閉包,很多時候我們只是沒有意識到這樣一個概念,其實很多地方已經都在用了
// 最簡單的情況,返回一個函式
function a () {
return function b () {}
}
複製程式碼
題目來一個
var m = 1;
var obj = {
m: 2,
fn: function() {
return function() {
console.log(this.m)
}
}
}
obj.fn()() // 1
複製程式碼
var m = 1;
var obj = {
m: 2,
fn: function() {
const that = this
return function() {
console.log(that.m)
}
}
}
obj.fn()() // 2
複製程式碼
雖發表於此,卻畢竟為一人之言,又是每日學有所得之筆記,內容未必詳實,看官老爺們還望海涵。