小小知識點(diǎn):
-
typeof是一個操作符烹看,并不是function澄耍。所以typeof后面不用加括號番官。
參見MDN
image.png 關(guān)于 typeof null // Object
null 值表示一個空對象指針降传,而這也正是使用 typeof 操作符檢測 null 值時(shí)會返回"object"的原因羞芍,如只聲明不賦值堂竟,則默認(rèn)值為undefined
-
關(guān)于const
const num = 1; num = 2; //Uncaught TypeError: Assignment to constant variable. const obj = { data: 1 } obj.data = 2;//沒有報(bào)錯
咦魂毁,不是說const定義的常量不能被改變嗎?
const僅保證指針不發(fā)生改變出嘹,修改對象的屬性不會改變對象的指針席楚,除非我們直接重寫對象也就是說const定義的引用類型只要指針不發(fā)生改變,其他的不論如何改變都是允許的税稼。所以const obj = { data:1 } obj = { data:4 }//Uncaught TypeError: Assignment to constant variable.
-
關(guān)于arguments烦秩,我們更推薦這么使用
這時(shí)的args就是真正的數(shù)組了。function foo(...args){ return args; } foo(1,2,3)//[1,2,3]
函數(shù)的返回值有兩種結(jié)果:
顯示調(diào)用return 返回 return 后表達(dá)式的求值
沒有調(diào)用return 返回 undefined關(guān)于this
在普通函數(shù)中
嚴(yán)格模式下娶聘,this指向undefined
非嚴(yán)格模式下闻镶,this指向全局對象(Node中的global,瀏覽器中的window)
在ES6中默認(rèn)使用嚴(yán)格模式丸升,所以在普通函數(shù)中你會發(fā)現(xiàn)this指向undefined铆农。語句和表達(dá)式
JavaScript是一種語句優(yōu)先的語言
逗號運(yùn)算符的左右兩側(cè)都必須是表達(dá)式!如果是a:1狡耻,那肯定會報(bào)錯墩剖。
逗號表達(dá)式的一般形式是:表達(dá)式1,表達(dá)式2夷狰,表達(dá)式3……表達(dá)式n
逗號表達(dá)式的求解過程是:先計(jì)算表達(dá)式1的值岭皂,再計(jì)算表達(dá)式2的值,……一直計(jì)算到表達(dá)式n的值沼头。最后整個逗號表達(dá)式的值是表達(dá)式n的值爷绘。
看下面幾個例子:
x=8*2,x*4 /整個表達(dá)式的值為64,x的值為16/
(x=8*2,x*4),x2 /整個表達(dá)式的值為128进倍,x的值為16/
x=(z=5,5*2) /整個表達(dá)式為賦值表達(dá)式土至,它的值為10,z的值為5/
x=z=5,5*2 /整個表達(dá)式為逗號表達(dá)式猾昆,它的值為10陶因,x和z的值都為5*/
逗號表達(dá)式用的地方不太多,一般情況是在給循環(huán)變量賦初值時(shí)才用得到垂蜗。所以程序中并不是所有的逗號都要看成逗號運(yùn)算符楷扬,尤其是在函數(shù)調(diào)用時(shí),各個參數(shù)是用逗號隔開的贴见,這時(shí)逗號就不是逗號運(yùn)算符烘苹。
有時(shí)候會遇到一些很奇怪的錯誤,就盡量往語句優(yōu)先這個方向想蝇刀。
-
立即執(zhí)行函數(shù)
function(){}() //Uncaught SyntaxError: Unexpected token (
這里為什么會報(bào)錯呢螟加?如果不加第一對括號,無論是function(){ /* 代碼 */ }();
還是
function(){ /* 代碼 */ }
都是會報(bào)錯的吞琐。因?yàn)閖s的引擎會把這里的 function 看成是函數(shù)聲明捆探,而函數(shù)聲明不允許沒有函數(shù)名,因此會對匿名函數(shù)報(bào)錯站粟。
匿名函數(shù)只允許以表達(dá)式的形式存在黍图,例如:
setTimeout(function(){ /* 代碼 */ }, 1000);
這里的匿名函數(shù)就是作為 setTimeout 的一個參數(shù),是表達(dá)式奴烙,這種寫法是允許的助被。
或者:
var foo = function(){ /* 代碼 */ };
也就是說我們稍微修改一下,var fn = function(){}()切诀,也不會報(bào)錯揩环。
這是為什么?
因?yàn)檫@是把一個匿名函數(shù)賦值給一個變量幅虑,匿名函數(shù)在該語句中充當(dāng)函數(shù)表達(dá)式的角色丰滑。如果這里的函數(shù)有名字呢?不會報(bào)錯倒庵,但語義會發(fā)生變化褒墨。例如:
function foo(x){ /* 代碼 */ }(1);
其實(shí)這里的代碼就相當(dāng)于
function foo(x){ /* 代碼 */ }; (1);
原因是js引擎會認(rèn)為前面的函數(shù)是一個函數(shù)聲明的語句,而后面的(1)是另一個單獨(dú)的語句擎宝,于是執(zhí)行后面的語句郁妈,在控制臺輸出1。
js的括號有幾種不同的作用绍申,其中一種作用就是:表示在括號內(nèi)的是表達(dá)式而不是語句噩咪。具體到這個例子上,第一對括號就是告訴js引擎极阅,這里面的匿名函數(shù)是一個函數(shù)表達(dá)式胃碾,而不是函數(shù)聲明語句。因此加了這個括號之后涂屁,就不會報(bào)錯了书在。
(function(){})() //強(qiáng)制其理解為函數(shù),()里面放表達(dá)式拆又,“函數(shù)()”表示執(zhí)行該函數(shù)儒旬,即聲明后立即執(zhí)行了。 補(bǔ)充原型鏈的一個小知識:
每個原型對象prototype中都有一個constructor屬性帖族,默認(rèn)指向函數(shù)本身栈源。閉包和內(nèi)存泄露的問題
首先應(yīng)當(dāng)明確的是內(nèi)存泄漏可能是代碼的問題,而不是閉包的問題竖般,如果為了避免內(nèi)存泄漏而不去用閉包的話甚垦,有些問題是很難解決的。
在IE9之前,BOM和DOM對象都是使用C++以COM對象的形式來實(shí)現(xiàn)的艰亮,COM對象的垃圾收集機(jī)制采用的就是計(jì)數(shù)策略闭翩,所以IE中只要涉及到BOM和DOM對象,就很可能會存在循環(huán)引用的問題迄埃。
閉包實(shí)際上很容易造成JavaScript對象和DOM對象的隱蔽循環(huán)引用疗韵。也就是說,只要閉包的作用域鏈中保存著一個HTML元素侄非,那么就意味著這個元素將無法被銷毀蕉汪。例如下面的:
function assignHandler(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert (element.id);
};
}
注意,閉包的概念是一個函數(shù)的返回值是另外一個函數(shù)逞怨,返回的那個函數(shù)如果調(diào)用了其父函數(shù)內(nèi)部的其他值者疤,這是一個element元素事件處理的一個閉包。這個閉包創(chuàng)建了一個循環(huán)引用叠赦。
- JavaScript對象element引用了一個DOM對象(其id為“someElement”)驹马; JS(element) ----> DOM(someElemet)
- 該DOM對象的onclick屬性引用了匿名函數(shù)閉包,而閉包可以引用外部函數(shù)assignHandler 的整個活動對象眯搭,包括element 窥翩; DOM(someElement.onclick) ---->JS(element)
匿名函數(shù)一直保存著對assginHandler()活動對象的引用,它所占的內(nèi)存永遠(yuǎn)不會被回收鳞仙。
可以對代碼稍稍改進(jìn):
function assignHandler(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert (id);
};
element = null;
}
可以首先通過引用一個副本變量消除循環(huán)引用寇蚊,但是這個閉包包含外部函數(shù)的全部活動對象,所以就算不直接引用棍好,匿名函數(shù)一直都包含著對element的引用仗岸。所以最后需要手動設(shè)置element變量為null.
閉包的很大作用是可以讀取函數(shù)內(nèi)部的變量,另一個就是讓這些變量的值始終保持在內(nèi)存中借笙。
只要變量被任何一個閉包使用了扒怖,就會被添到詞法環(huán)境中,被該作用域下所有閉包共享业稼。
絕對不能因?yàn)閮?nèi)存泄漏而不使用閉包盗痒。
JS中只要越過了閉包,作用域低散,原型這幾座大山俯邓,對JS的理解就高了一個層次了!
作用域的文章可以查看下面的網(wǎng)址H酆拧;蕖!https://lemontency.github.io/2019/03/20/%E5%8E%9F%E5%9E%8B%E4%B8%8E%E5%8E%9F%E5%9E%8B%E9%93%BE/
最后來幾道作用域和this的題吧引镊!
if(!(username in window)){
var username = 'zty';
}
console.log(username) //undefined
怎么理解呢朦蕴。首先在ES5的代碼中并沒有塊級作用域的概念篮条,var username直接變量提升到最頂部,此時(shí)全局作用域中有username并且值為undefined吩抓,所以!(username in window)為false涉茧,跳過賦值部分,username依舊是undefined琴拧。
//差點(diǎn)就錯了
//執(zhí)行順序是
//聲明函數(shù)foo降瞳,注意是整個函數(shù)一起提升上去的
//調(diào)用函數(shù)foo
//聲明變量test
//console.log(test)
//test = "bbb"
//console.log(test)
var test = "aaa";
function foo(){
console.log(test);
var test = "bbb";
console.log(test);
}
foo(); //undefined bbb
console.log(test)//aaa
也就是說上面的代碼的執(zhí)行順序其實(shí)是這樣的:
var test = aaa;
function foo(){
var test;
console.log(test);
var test = "bbb";
console.log(test);
}
foo();
console.log(test);
var name = 'global';
function A(name){
//傳參的優(yōu)先級要高于后面的聲明
//雖然后面有變量提升嘱支,但是是沒有影響的
alert(name);//3
this.name = name;
var name = '1';
}
A.prototype.name = '2';
var a = new A('3');
alert(a.name);//3
delete a.name;//刪除對象的屬性但是不刪除對象原型鏈上的屬性
alert(a.name);//2
比較好的實(shí)踐就是把要用到的變量聲明都寫到函數(shù)最前面蚓胸。
再來看一道容易做的題:
function fun(n,o){
console.log(o);
return {
fun:function(m){
return fun(m,n)
}
}
}
var a = fun(0);//undefined
a.fun(1);
a.fun(2);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1);
c.fun(2);c.fun(3);
我們看到:return返回的對象的fun屬性對應(yīng)一個新建的函數(shù)對象,這個函數(shù)對象將形成一個閉包作用域除师,使其能夠訪問外層函數(shù)的變量n及外層函數(shù)fun,為了不讓fun屬性和fun對象混淆沛膳,先更改一下代碼:
function _fun_(n,o){
console.log(o);
return {
fun:function(m){
return _fun_(m,n)
}
}
}
var a = _fun_(0);//undefined
a.fun(1);//
a.fun(2);
var b = _fun_(0).fun(1).fun(2).fun(3);
var c = _fun_(0).fun(1);
c.fun(2);c.fun(3);
這道題難度挺大,但是一定要做汛聚。
講師小tips:每次調(diào)用fun就相對應(yīng)的在紙上還原作用域鏈锹安。
_fun_函數(shù)執(zhí)行,因?yàn)榈?個參數(shù)未定義,輸出undefined倚舀。然后返回一個對象叹哭,帶有fun屬性,指向一個函數(shù)對象痕貌,帶有閉包,能夠訪問到_fun_和變量n风罩。
a.fun(1)執(zhí)行返回的對象的fun方法,傳入m的值1舵稠,調(diào)用返回_fun_(1,0)超升,打印出0;
a.fun(1)執(zhí)行返回的對象的fun方法哺徊,傳入m的值1室琢,調(diào)用返回_fun_(2,0),打印出0落追;
接下來的var b = _fun_(0).fun(1).fun(2).fun(3);可以等價(jià)為
var b = _fun_(0);
var b1 = b.fun(1);
var b2 = b1.fun(2);
var b3 = b2.fun(3);
前兩句的和我們剛剛分析的情況是一樣的盈滴,var b = _fun_(0);首先返回一個對象,帶有fun屬性轿钠,指向一個函數(shù)對象巢钓,帶有閉包,能夠訪問_fun_和變量n谣膳。
b.fun(1)傳入m的值為1竿报,調(diào)用返回_fun_(1,0),打印出0继谚;
b1.fun(2)傳入m的值為2烈菌,調(diào)用返回_fun_(2,1),打印出1;
b2.fun(3)傳入m的值為3芽世,調(diào)用返回_fun_(3,2)挚赊,打印出2;
接下來var c = _fun_(0).fun(1);情況和前面的是一樣的济瓢,先打印出undefined荠割,再打印出0;
c.fun(2)傳入m的值為2,調(diào)用返回_fun_(2,1)旺矾,打印出1蔑鹦;
c.fun(3)傳入m的值為,調(diào)用返回_fun_(3,1)箕宙,打印出1嚎朽;
還要注意,這里傳遞的是一個簡單的數(shù)據(jù)類型柬帕,而不是引用的數(shù)據(jù)類型哟忍。
看看這個?
https://lemontency.github.io/2019/03/20/JS%E4%B8%AD%E7%9A%84%E6%8C%89%E5%80%BC%E4%BC%A0%E9%80%92%E5%92%8C%E6%8C%89%E5%BC%95%E7%94%A8%E9%97%AE%E9%A2%98-1/
參考:https://www.zhihu.com/question/48238548
https://segmentfault.com/a/1190000004187681