摘要:走在前端的大道上本篇是一篇文章帶你理解原型和原型鏈一篇文章帶你完全理解的查漏補缺,會不斷豐富提煉總結更新。
走在前端的大道上
本篇是 一篇文章帶你理解原型和原型鏈 、一篇文章帶你完全理解this的查漏補缺,會不斷豐富提煉總結更新。
什么是原型鏈原型鏈 是針對構造函數的,比如我先創建了一個函數,然后通過一個變量new了這個函數,那么這個被new出來的函數就會繼承創建出來的那個函數的屬性,然后如果我訪問new出來的這個函數的某個屬性,但是我并沒有在這個new出來的函數中定義這個變量,那么它就會往上(向創建出它的函數中)查找,這個查找的過程就叫做原型鏈。
Object ==> 構造函數1 ==> 構造函數2
就和css中的繼承一樣,如果自身沒有定義就會繼承父元素的樣式。
function a(){}; a.prototype.name = "追夢子"; var b = new a(); console.log(b.name); //追夢子什么是作用域鏈
作用域 是針對變量的,比如我們創建了一個函數,函數里面又包含了一個函數,那么現在就有三個作用域
全局作用域==>函數1作用域==>函數2作用域
作用域的特點:先在自己的變量范圍中查找,如果找不到,就會沿著作用域往上找。
如:
var a = 1; function b(){ var a = 2; function c(){ var a = 3; console.log(a); } c(); } b();
最后打印出來的是3,因為執行函數c()的時候它在自己的范圍內找到了變量a所以就不會越上繼續查找,如果在函數c()中沒有找到則會繼續向上找,一直會找到全局變量a,這個查找的過程就叫作用域鏈。
不知道你有沒有疑問,函數c為什么可以在函數b中查找變量a,因為函數c是在函數b中創建的,也就是說函數c的作用域包括了函數b的作用域,當然也包括了全局作用域,但是函數b不能向函數c中查找變量,因為作用域只會向上查找。
上下文console.log(a); // Uncaught ReferenceError: a is not defined // 因為沒有定義a所以報錯了。
var a = 52; console.log(a); //52 // 有定義a,并且給a賦值了52所以打印a就是52。
console.log(a); //undefined var a = 52;
雖然有定義a但是打印卻在變量a的前面,那為什么不是報錯而是打印出來的是undefined?因為在js執行代碼之前,js會先獲取到所有的變量并且把這些變量放置到js代碼的頂部。(簡稱變量聲明提前)
我們給賦值給a的52到哪去了。雖然我前面說了js會事先獲取所有的變量并且將這些變量放置到頂部,但是 變量的賦值并不會事先執行 ,也就是說,在哪聲明的變量,這個變量的賦值就在哪執行。
實際上,上面的代碼是這樣執行的:
var a; console.log(a); //undefined a=52;
console.log(a); function a(){ this.user = "追夢子"; } //為什么,可以事先就打印出函數a呢?
因為 函數的賦值在函數聲明的時候 就已經賦值了,結合上面我說的變量提前,那是不是就可以理解這句話了?
function a(){ this.user = "追夢子"; } console.log(a); //正常
a(); //Uncaught TypeError: a is not a function var a = function(){ console.log(52); } //為什么現在又不行了?
因為現在的函數已經賦值給了變量a,現在 它的執行過程和變量一樣 了,我們通常把這種函數賦值給變量的形式叫做 函數表達式。
var a = function(){ console.log(52); } a(); //52 //正常
if(false){ var a = 1; } console.log(a); //undefined
之所以沒有報錯而是輸出了undefined是因為 變量存在預解析 的情況,又因為 js沒有塊級作用域,所以最后代碼就成了這樣
var a; if(false){ a = 1; } console.log(a);
總結:
函數分為:函數聲明和函數表達式。
函數聲明
function a(){ alert("追夢子博客"); }
函數表達式
var a = function(){ alert("追夢子"); }
看似兩段相同的語句,它們的執行順序卻截然不同,函數聲明時的賦值行為是在函數創建的時候進行的,而函數表達式的賦值行為是在執行這句變量時進行的(因為它已經賦值給了變量所以我這里把它稱為變量)。
不管是變量還是函數都會存在變量聲明提前。
來看看幾題有意思的js例題,加以理解
var a = 1; function b(){ console.log(a); //undefined var a = 5; } b();
為什么打印的是undefined?
我們先來看看它的解析過程:
var a = 1; function b(){ var a console.log(a); //undefined a = 5; } b();
我們一起來看看另外一題比較有難度的js面試題:
var a = 1; function b() { a = 120; return; function a() {} } b(); alert(a); //1;
如果你看了上面一題我相信你應該有種不知所措的感覺,這里現在為什么又是1了呢?
我把執行過程的代碼寫出來我相信你就懂了。
var a = 1; function b() { var a; a = 120; return; function a() {} } b(); alert(a);
如果你正在js的進階階段肯定更悶了,你肯定會想我們不是寫return了嗎?return后面的代碼不是不執行嗎?為什么這里卻影響了這段代碼?
雖然return后面的代碼不會被執行,但是在js預解析的時候(變量提升的時候)還是會把return后面的變量提前,所以我們這段代碼 因為變量提前所以函數里面的變量a就成了局部變量,因為函數外部是訪問不了函數內部的變量所以就輸出了1。
另外提兩點,函數的arguments和函數名都是直接賦值的,也就是在這個函數解析的時候就會進行賦值。
作用域的進階什么是自由變量?
如我在全局中定義了一個變量a,然后我在函數中使用了這個a,這個a就可以稱之為自由變量,可以這樣理解,凡是跨了自己的作用域的變量都叫 自由變量。
var a = "追夢子"; function b(){ console.log(a); //追夢子 } b();
上面的這段代碼中的變量a就是一個自由變量,因為在函數b執行到console.log(a)的時候,發現在函數中找不到變量a,于是就往上一層中找,最后找到了全局變量a。
作用域的進階
在我講作用域鏈的時候說過如果有一個全局變量a,以及函數中也有一個變量a,那么只會作用函數中的那個變量a,都是有一種情況就顯得比較復雜一些,我們一起來看看這段代碼。
var aa = 22; function a(){ console.log(aa); } function b(fn){ var aa = 11; fn(); } b(a); //22
最后打印的不是11而是22,為什么會這樣呢?一起來分析一下這段代碼。
假如我們的代碼是這樣的
var aa = 22; function a(){ console.log(aa); }
打印出的是22,我想大家應該沒有意見,但是有一點我一定要提,那就是 在創建這個函數的時候,這個函數的作用域就已經決定了,而是不是在調用的時候,這句話至管重要。
分析一下過程,首先我們創建了一個全局變量aa
var aa = 22;
接著我們創建了一個函數a
function a(){ console.log(aa); }
這時js解析這個函數的時候,就已經決定了這個函數a的作用域,既如果在函數a中找不到變量aa那就會到全局變量中找,如果找到了就返回這個aa,如果找不到就報錯。
接著我們又創建了一個函數b
function b(fn){ var aa = 11; fn(); }
在函數b中我們定義了又重新定義了這個變量aa,雖然我們這個時候重新定義了變量aa,但是因為函數a的作用域在創建的時候已經決定了,所以在函數b中創建的那個變量aa以及和函數a里面的那個變量aa沒有關系了。
function b(fn){ var aa = 11; fn(); } b(a);
我們把函數a傳到了函數b中,并且當做函數b的形參,接著我們執行了這個被傳進去的函數a,最后打印出來的就是22。
在創建這個函數的時候,這個函數的作用域就已經決定了,而是不是在調用的時候。
筆者注: 看到這句話是不是似曾相識?this的指向在函數定義的時候是確定不了的,只有函數執行的時候才能確定this到底指向誰,實際上this的最終指向的是那個調用它的對象
一個是作用域,一個是上下文
舉個例子回顧對比一下
box.onclick = function(){ function fn(){ alert(this); } fn(); };
我們原本以為這里面的this指向的是box,然而卻是Window。一般我們這樣解決,將this保存下來:
box.onclick = function(){ var _this = this; function fn(){ alert(_this); } fn(); };
還有一些情況,有時我們想讓偽數組也能夠調用數組的一些方法,這時call、apply、bind就派上用場了。
我們先來解決第一個問題修復this指向。
box.onclick = function(){ function fn(){ alert(this); } fn(); };
改成如下:
box.onclick = function(){ function fn(){ console.log(this); } fn.call(this); };
很神奇吧,call的作用就是改變this的指向的,第一個傳的是一個對象,就是你要借用的那個對象。
fn.call(this);
這里的意思是 讓this去調用fn這個函數,這里的this是box,這個沒有意見吧?box調用fn,這句話非常重要,我們知道 this始終指向一個對象,剛好box就是一個對象。那么fn里面的this就是box。
可以簡寫的,比如:
box.onclick = function(){ var fn = function(){ console.log(this); //box }.call(this); };
或者這樣:
box.onclick = function(){ (function(){ console.log(this); }.call(this)); //box };
又或者這樣:
var objName = {name:"JS2016"}; var obj = { name:"0 _ 0", sayHello:function(){ console.log(this.name); }.bind(objName) }; obj.sayHello();//JS2016
call和apply、bind但是用來改變this的指向的,但也有一些小小的差別。下面我們來看看它們的差別在哪。
call和apply、bind但是用來改變this的指向的,但也有一些小小的差別。下面我們來看看它們的差別在哪。
function fn(a,b,c,d){ console.log(a,b,c,d); } //call fn.call(null,1,2,3); //apply fn.apply(null,[1,2,3]); //bind var f = fn.bind(null,1,2,3); f(4);
結果如下:
1 2 3 undefined 1 2 3 undefined 1 2 3 4
前面說過第一個參數傳的是一個你要借用的對象,但這么我們不需要,所有就傳了一個null,當然你也可以傳其他的,反正在這里沒有用到,除了第一個參數后面的參數將作為實際參數傳入到函數中。
call就是挨個傳值,apply傳一個數組,bind也是挨個傳值,call和apply會直接執行這個函數,而bind并不會而是將綁定好的this重新返回一個新函數,什么時候調用由你自己決定。
var objName = {name:"JS2016"}; var obj = { name:"0 _ 0", sayHello:function(){ console.log(this.name); }.bind(objName) }; obj.sayHello();//JS2016
這里也就是為什么我要用bind的原因,如果 用call的話就會報錯了。自己想想這個sayHello在obj都已經執行完了,就根本沒有sayHello這個函數了。
這幾個方法使用的好的話可以幫你解決不少問題比如:
正常情況下Math.max只能這樣用
Math.max(10,6)
但如果你想傳一個數組的話你可以用apply
var arr = [1,2,30,4,5]; console.log(Math.max.apply(null,arr));
又或者你想讓偽數組調用數組的方法
function fn(){ [].push.call(arguments,3); console.log(arguments); //[1, 2, 3] } fn(1,2);
再者:
var arr = ["aaabc"]; console.log("".indexOf.call(arr,"b")); //3
參考文章:
1.什么是作用域鏈,什么是原型鏈,它們的區別,在js中它們具體指什么?
2.js中的執行上下文,菜鳥入門基礎
3.JS中call、apply、bind使用指南,帶部分原理。
3.理解js中的自由變量以及作用域的進階
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90328.html
摘要:在之前我們根絕對象的原型說過了的原型鏈,那么同樣的萬物皆對象,函數也同樣存在這么一個鏈式的關系,就是函數的作用域鏈作用域鏈首先先來回顧一下之前講到的原型鏈的尋找機制,就是實例會先從本身開始找,沒有的話會一級一級的網上翻,直到頂端沒有就會報一 在之前我們根絕對象的原型說過了js的原型鏈,那么同樣的js 萬物皆對象,函數也同樣存在這么一個鏈式的關系,就是函數的作用域鏈 作用域鏈 首先先來回...
摘要:而作用域鏈則控制著變量與函數的可見性和生命周期。三延長作用域鏈在中,和關鍵字能延長作用域鏈,對來說,將會指定一個只讀對象添加到作用域鏈中。 本文共 1200 字,讀完只需 4 分鐘 概述 JavaScript 中的可執行代碼有其執行上下文,在執行上下文中,有三個重要的元素: 變量對象(variable object) 作用域鏈(scope chain) this 其中,變量對象是上...
摘要:該對象包含了函數的所有局部變量命名參數參數集合以及,然后此對象會被推入作用域鏈的前端。如果整個作用域鏈上都無法找到,則返回。此時的作用域鏈包含了兩個對象的活動對象和對象。 前端學習:教程&開發模塊化/規范化/工程化/優化&工具/調試&值得關注的博客/Git&面試-前端資源匯總 歡迎提issues斧正:閉包 JavaScript-閉包 閉包(closure)是一個讓人又愛又恨的somet...
摘要:對于直接量和局部變量的訪問性能差異微不足道,性能消耗代價高一些的是全局變量數組項對象成員。當一個函數被創建后,作用域鏈中被放入可訪問的對象。同樣會改變作用域鏈,帶來性能問題。 早前閱讀高性能JavaScript一書所做筆記。 一、Loading and Execution 加載和運行 從加載和運行角度優化,源于JavaScript運行會阻塞UI更新,JavaScript腳本的下載、解析...
摘要:對于直接量和局部變量的訪問性能差異微不足道,性能消耗代價高一些的是全局變量數組項對象成員。當一個函數被創建后,作用域鏈中被放入可訪問的對象。同樣會改變作用域鏈,帶來性能問題。 早前閱讀高性能JavaScript一書所做筆記。 一、Loading and Execution 加載和運行 從加載和運行角度優化,源于JavaScript運行會阻塞UI更新,JavaScript腳本的下載、解析...
閱讀 3206·2021-11-19 09:40
閱讀 3005·2021-09-09 09:32
閱讀 792·2021-09-02 09:55
閱讀 1393·2019-08-26 13:23
閱讀 2403·2019-08-26 11:46
閱讀 1229·2019-08-26 10:19
閱讀 2054·2019-08-23 16:53
閱讀 1072·2019-08-23 12:44