第一章
編譯原理
js是一門編譯語言
傳統(tǒng)編譯語言流程:
- 分詞/詞法分析:把字符串分解成有意義的代碼塊
- 解析/語法分析:把詞法單元流(數(shù)組)轉(zhuǎn)換成一個由元素逐級嵌套所組成的“抽象語法樹”
- 代碼生成
js編譯:
編譯過程不是發(fā)生在構(gòu)建之前的,而是發(fā)生在代碼執(zhí)行前的幾微秒內(nèi)
理解作用域
- 引擎:負責(zé)整個js程序的編譯和執(zhí)行
- 編譯器:負責(zé)語法分析及代碼生成(臟活累活)
- 作用域:負責(zé)收集并維護由所有聲明的標識符(變量)組成的一系列查詢,并實施一套非常嚴格的規(guī)則,確定當(dāng)前執(zhí)行的代碼對這些標識符的訪問權(quán)限。
變量的賦值操作:
步驟一:編譯器在當(dāng)前作用域中聲明一個變量(如果之前沒聲明過)
步驟二:運行時,引擎在作用域中查找該變量,如果可以找到就對它賦值
引擎對變量進行LHS查詢,另一個查找類型叫RHS查詢----L代表左側(cè),R代表右側(cè)
指的是賦值操作的左側(cè)和右側(cè),即
變量出現(xiàn)在賦值操作的左側(cè)時進行LHS操作,出現(xiàn)在右側(cè)時進行RHS操作
RHS
- consol.log(a)時,對a進行RHS查詢,問,a的值是多少(retrieve his sourse value)---
- b =2 ; 對a進行LHS查詢,問,a是神馬----
1.2.5的小測驗
function foo(a){
var b = a;
return a + b;
}
var c = foo(2);
執(zhí)行過程:
- 對foo(2)進行RHS查找,執(zhí)行foo(2)
- a =2 對a 進行LHS ,進入函數(shù),
- var b = a ,對a進行RHS查找 ,然后對b進行LHS查找
- return a + b 對a 和b各進行一次RHS,退出函數(shù)
- var c=foo(2);對c進行一次LHS查找
總計,3次LHS,4次RHS
- 作用域嵌套查找
區(qū)分RHS和LHS的意義:
RHS失敗會拋出 ReferenceError 引用異常
LHS失敗會自動隱式的創(chuàng)建全局變量(非嚴格模式下)
RHS之后如果嘗試對變量進行不合理的操作,會拋出TypeError,這表明作用域判別成功,但是對結(jié)果的操作不合法
第二章
詞法作用域:定義在詞法階段的作用域---
作用域氣泡
- 作用域查找:會在第一個匹配的標識符處停止,因此多層嵌套可以定義同名的標識符,這叫“遮蔽效應(yīng)”
全局變量會自動成為全局對象(比如widow)的屬性,因此可以通過window.a來訪問那些被同名變量所遮蔽的全局變量
欺騙詞法-(性能會下降)
-
eval( ) 把內(nèi)部字符串視為,好像書寫時就存在于那里----動態(tài)插入(嚴格模式中eval()有自己的詞法作用域,無法修改所在的作用域)
setTimeout(...)和setInterval(...)第一個參數(shù)可以被解釋成動態(tài)生成的函數(shù)代碼,new Function(...)也很類似最后一個參數(shù)可以接受代碼字符串,并轉(zhuǎn)化成動態(tài)生成的函數(shù),比eval安全一些
-
with 不需要重復(fù)引用對象本身
with語句內(nèi)部,當(dāng)對一個不包含某屬性的對象進行with時,會創(chuàng)建一個全局作用域的變量屬性(非嚴格模式),嚴格模式完全禁止with
影響引擎優(yōu)化的處理方案,沒有eval()和with,引擎依賴代碼的詞法進行靜態(tài)分析,預(yù)先確定變量和函數(shù)的定義位置,但是如果有eval()和with,有關(guān)標識符位置的判斷會被判斷為無效的
第三章
- 函數(shù)作用域 屬于這個函數(shù)的全部變量都可以在整個函數(shù)的范圍內(nèi)使用和復(fù)用,在嵌套的作用域中也可以
- 基于作用域的隱藏方法 可以用函數(shù)來隱藏內(nèi)部實現(xiàn),把它包裹在函數(shù)內(nèi),外面就無法訪問到了
- 好處一:
最小特權(quán)原則(最小授權(quán)、最小暴露原則)
- 好處二:避免同名標識符之間的沖突
應(yīng)用
- 全局命名空間
- 模塊管理
封裝原始版本:
var a = 2;
function foo(){
var a = 3;
console.log( a);
}
foo();
console.log(a); //2
---不太理想,因為需要聲明一個foo(),還需要顯式的調(diào)用這個函數(shù)才能運行
改進版
var a =2;
(function foo(){
var a = 3;
console.log(a);
})(); //注意這一行
console.log(a);
以(function...而不是 function ...開始,函數(shù)會被當(dāng)做函數(shù)表達式而不是一個標準的函數(shù)聲明來處理,這是重要區(qū)別
區(qū)分函數(shù)聲明和表達式最簡單的方法是看function關(guān)鍵字出現(xiàn)在聲明中的位置,如果function是聲明的第一個詞,那么就是一個函數(shù)聲明----- 以! 、+ 、-、開頭的都是這個邏輯,把函數(shù)聲明變?yōu)榱撕瘮?shù)表達式
(function foo(){...})作為函數(shù)表達式意味著foo只能在(...)所代表的位置中被訪問,外部不行
匿名和具名
- 函數(shù)表達式可以匿名,函數(shù)聲明不可以
- 行內(nèi)函數(shù)表達式
- 立即執(zhí)行函數(shù)表達式 IIFE Immediately Invoked Function Expression
塊作用域
- with 是一個塊作用域的一種形式
- try catch語句中catch會創(chuàng)建一個塊作用域
let關(guān)鍵字
let為其聲明的變量隱式的指定了所在的塊作用域
可以顯式的用{...}創(chuàng)建塊,使 其與其他語言中的塊作用域工作原理一致
- let的聲明不會在塊作用域內(nèi)進行提升,此代碼運行之前,聲明并不存在
{ console.log(bar); // ReferenceError!
let bar = 2;
}
- 與垃圾回收機制有關(guān),便于清楚垃圾
let的應(yīng)用 ----循環(huán)中
const關(guān)鍵字
也創(chuàng)建塊作用域變量
第四章
1、 a =2; var a ; console.log (a); //2
2、console.log(a); var a = 2; //undefined
編譯器
引擎會在解釋js代碼之前先對其進行編譯----編譯階段的一個任務(wù)就是,找到所有的聲明,并用合適的作用域?qū)⑺麄冴P(guān)聯(lián)起來,因此包括變量和函數(shù)在內(nèi)的所有聲明都會在任何代碼被執(zhí)行之前首先被處理。
var a= 2也許看起來是一個聲明,但是js會將其理解為2個聲明,var a和a= 2,第一個在編譯階段進行,第二個會被留在原地等待執(zhí)行階段,這解釋了第二個例子,a只聲明了,沒有被賦值,賦值操作在console語句之后進行
- 每個作用域都會進行提升操作
- 函數(shù)表達式不會被提升操作
- 函數(shù)會首先被提升,然后才是變量
- 普通塊內(nèi)部的函數(shù)聲明會被提升至所在作用域的頂部,這個過程不會被暗示的條件判斷控制
foo(); //"b"
var a = true;
if(a){
function foo(){ console.log("a") }
}else{
function foo() {console.log("b");}
}
第五章
閉包定義:
當(dāng)函數(shù)可以記住并訪問所在的詞法作用域時,就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行
閉包使得函數(shù)可以繼續(xù)訪問定義時的詞法作用域
經(jīng)典閉包題:
for (var i=1;i<=5;i++){
setTimeout( function timer(){
console.log(i);
},i*1000);
}
結(jié)果是以每秒一次的頻率輸出5次6
模塊
一個從函數(shù)調(diào)用所返回的,只有**數(shù)據(jù)屬性**而沒有**閉包函數(shù)**的對象并不是真正的模塊
模塊模式的兩個必要條件:
- 必須有外部的封閉函數(shù),該函數(shù)必須至少被調(diào)用一次(每次調(diào)用都會創(chuàng)建一個新的模塊實例)。
- 封閉函數(shù)必須返回至少一個內(nèi)部函數(shù),這樣內(nèi)部函數(shù)才能在私有作用域中形成閉包,并且可以訪問或者修改私有的狀態(tài)。
模塊模式的兩個特點:
- 為函數(shù)定義引入包裝函數(shù)(module.define(name,function(){}))
- 保證它的返回值和模塊的API保持一致
ES6為模塊增加了一級語法支持
瀏覽器對es6的支持,需要額外添加babel轉(zhuǎn)換------下載babel后把babel鏈入文件,并把js文件類型改為type="text/babel",就可以了
附錄A
js是基于詞法作用域的語言,在bar內(nèi)調(diào)用foo時,foo基于自己的作用域鏈進行查找
function foo(){
console.log(a); //////////2(不是3)
}
function bar(){
var a=3;
foo();
}
var a=2;
bar();
而this的運行機制是動態(tài)作用域
- 詞法作用域關(guān)注函數(shù)在何處聲明
- 動態(tài)作用域關(guān)注函數(shù)從何處調(diào)用
附錄C
var foo=a=>{
console.log(a);
}
foo(2);///////2
- 胖箭頭寫法代表function
- 箭頭函數(shù)在涉及this綁定時的行為和普通函數(shù)的行為完全不同,它放棄了所有普通this綁定的規(guī)則,取而代之的是用當(dāng)前的詞法作用域覆蓋了this本來的值
第二部分第一章
在函數(shù)內(nèi)部引用自身,可以用指向函數(shù)對象的詞法標識符來引用,但是匿名函數(shù)沒有詞法標識,就不行了,另一種是arguments.callee來應(yīng)用當(dāng)前正在運行的函數(shù)對象,對匿名函數(shù)也有效。
this是什么
this是在運行時進行綁定的,它的上下文取決于函數(shù)調(diào)用時的各種條件
第二章
- 函數(shù)名稱引用中會丟失this
- 隱式賦值中會丟失this
- 回調(diào)函數(shù)會丟失this
通過call()和apply()方法來顯式的綁定this
判斷this
- 是否在new中調(diào)用--
- 是否通過call,apply或者硬綁定
- 是否在某個上下文對象中調(diào)用(隱式綁定)
- 如果都不是,那么使用默認綁定
es6中可以用...操作符來代替apply(...)展開數(shù)組,foo(...[1,2])和foo(1.2)是一樣的
`創(chuàng)建一個空對象比較安全 Object.create(null);
箭頭 函數(shù)不會使用四條標準的綁定規(guī)則,和self=this機制一樣,繼承外層函數(shù)調(diào)用的this綁定
第三章 對象
定義:聲明形式和構(gòu)造形式
聲明形式:
var myObj={
key:value
/ / ...
};
構(gòu)造形式:
var myObj= new Object();
myObj.key=value;
簡單基本類型:string boolean number null undefined ——————并不是對象
(null 執(zhí)行typeof會返回object,是因為不同對象底層表示為二進制,二進制前三位都為0的話會被判斷為object,null的二進制表示全是0,因此會被錯誤判斷)
函數(shù)和數(shù)組都是對象的一種類型
但是有對象子類型,叫內(nèi)置對象:
String Number Boolean Object Function Array Date RegExp Error
對象的屬性:
屬性名稱與值,屬性名稱存在對象容器內(nèi)部,但是值一般不存在對象內(nèi)部,而是通過屬性名稱作為指針指向它的存儲位置。
兩種訪問方式:myObject.a 或者 myObject[a]
前者為屬性訪問,后者為鍵訪問
兩者有些區(qū)別,屬性訪問要求要滿足標識符的命名規(guī)范,因此Super-Fun!這種屬性名不符要求,由于后者是使用字符串訪問,因此可以在程序中構(gòu)造這個字符串
ES6新增了可計算屬性名
var prefix = "foo"
var myObject = {
[prefix + "bar" ] :"hello",
[prefix + "baz"] : "world"
};
對象的淺復(fù)制和深復(fù)制
es6的淺復(fù)制 Object.assign()
var newObj = Object.assign({},myObject);
淺復(fù)制值會復(fù)制舊對象的值,對象的話會引用自原對象
es5的屬性描述符:
Object.getOwnPropertyDescriptor(myObject,"a")
獲取myObject的a屬性的描述
三個特性:writable/configurable/enumerable
可修改/可配置/可列舉(比如是否出現(xiàn)在for --in中)
默認或者設(shè)置 Object.defineProperty(...)
- 禁止一個對象添加新屬性并保留已有屬性:
Object.preventExtensions(...) - 密封:Object.seal(...)可以修改現(xiàn)有屬性但是不能添加新屬性或重新配置或刪除現(xiàn)有的seal()=preventExtensions+confugurable:false
- 凍結(jié):Object.freeze(...)禁止對對象任意直接屬性的修改 freeze=seal+writable:false
var myObject= {
a:2
};
myObject.a;
這個過程中實現(xiàn)了[[Get]]操作,先在對象中查找是否有名稱相同的屬性,如果沒有,會繼續(xù)遍歷可能存在的原型鏈
還有一個[[Put]]
屬性的getter和setter:成對出現(xiàn)
var myObject = {
get a(){
return this.a;
},
set a(val){
this.a=val*2;
};
myObject.a = 2;
myObject.a; /////4
一個屬性undefined時,判斷是屬性中存著undefined還是屬性本身不存在
var myObject = {
a:2
};
("a" in myObject);/ /true
("b" in myObject);/ /false
或者
myObject.hasOwnProperty("a");/ /true
myObject.hasOwnProperty("b");/ /false
二者略有不同,in 操作符會檢查對象自身及其原型鏈(檢查是否有這個屬性名,而不是值,這個對于數(shù)組來說區(qū)別明顯 4 in[2,4,6]返回false),而hasOwnProperty顯然只檢查對象自身(如果對象沒有連接到Object.prototype這種方法會失敗,因此改進一下,Object.prototype.hasOwnProperty.call(myObject,"a")就可以了)
枚舉:指出現(xiàn)在對象的屬性遍歷中:
for (var k in myObject){
console.log(k,myObject[k]);
}
(數(shù)組的遍歷用傳統(tǒng)的for循環(huán)更好)
myObject.propertyIsEnumerable("a");/ /true
myObject.propertyIsEnumerable("b");/ /false
或者
Object.keys(myObject);會返回所有可枚舉屬性
Object.getOwnPropertyNames(myObject);會返回所有屬性
es6的for of來遍歷屬性的值:
for(var v of myArray){
}
用內(nèi)置的@@iterator迭代器來手動遍歷數(shù)組:
var myArray = [1,2,3];
var it = myArraySymbol.iterator;
it.next();/ / {value:1, done:false}
it.next();/ / {value:2, done:false}
it.next();/ / {value:3, done:false}
it.next();/ / { done:true}
@@iterator本身不是一個迭代器對象,而是一個返回迭代器對象的函數(shù),因此后邊需要跟一個()
手動為一個對象添加自己的迭代器
var myObject = {
a:2,
b:3
};
Object.defineProperty(myObject,Symbol.iterator,{
enumerable:false,
writable:false,
configurable:true,
value:function(){
var o = this;
var idx = 0;
var ks = Object.keys(o);
return{
next:function(){
return{
value:o[ks[idx++]],
done:(idx>ks.length)
}
}
}
}
})
第四章
js中的顯式混入對象:
function mixin(souceObj,targeObj){
for(var key in sourceObj){
if(!(key in targetObj)){
targetObj[key] = sourceObj[key];
}
}
js中不存在類,都是對象
函數(shù)多態(tài):顯式多態(tài)和相對多態(tài)
顯式多態(tài)缺點多,頻繁引用原函數(shù)
第五章 原型
myObject.foo = "bar"
會發(fā)生的事情:
- 如果myObject中已包含名為foo的普通數(shù)據(jù)訪問屬性,那么這條只會修改已有的屬性值
- 如果遍歷原型鏈也沒找到foo,那么會在myObject上添加foo
- 然而如果原型鏈上層有這條屬性,會出現(xiàn)3種情況:
A:如果上層存在普通數(shù)據(jù)訪問屬性,并且沒有被標記為只讀(writable:false),那么會直接在myObject中添加一個叫foo的新屬性
B:如果上層有且為只讀,那么會拋出錯誤或者被忽略
C:如果上層叫foo的是一個setter,那么會調(diào)用上層的setter
要注意避免隱式遮蔽:myObject.a++會隱式創(chuàng)建myObject.a
原型繼承:委托機制
foo foo.prototype foo.prototype.constructor
constructor是默認構(gòu)造foo時會添加給foo.prototype的屬性,如果人為構(gòu)造原型可能會丟失constructor
此外,var a=new foo();,a.constructor=foo并不代表,a是有constructor這個屬性,該屬性其實是[[Get]]查詢原型鏈從foo.prototype那里引用來的
b.prototype=Object.create(a.prototype)
會創(chuàng)建一個新對象并把新對象內(nèi)部的[[Prototype]]
關(guān)聯(lián)到指定的對象
es6可以設(shè)定原型了:
Object.setPrototypeOf(Bar.prototype,Foo.prototype)
b.isPrototypeOf(a)——表達:b是否出現(xiàn)在a的原型鏈中
直接獲取一個對象的[[Prototype]]鏈
Object.getPrototypeOf(a);
也有部分支持屬性法:
a._proto_===Foo.prototype;
第六章
[[Prototype]]機制把對象關(guān)聯(lián)到其他對象:
不同于類設(shè)計模式,方法名盡量不使用通用方法名,而是創(chuàng)建更有描述性的名字
注意區(qū)別類模式中的顯式偽多態(tài)調(diào)用方法與委托模式中的委托調(diào)用的區(qū)別