走在前端的大道上css
本篇是 一篇文章帶你理解原型和原型鏈 、一篇文章帶你徹底理解this的查漏補缺,會不斷豐富提煉總結更新。html
原型鏈 是針對構造函數的,好比我先建立了一個函數,而後經過一個變量new了這個函數,那麼這個被new出來的函數就會繼承建立出來的那個函數的屬性,而後若是我訪問new出來的這個函數的某個屬性,可是我並無在這個new出來的函數中定義這個變量,那麼它就會往上(向建立出它的函數中)查找,這個查找的過程就叫作原型鏈。前端
Object ==> 構造函數1 ==> 構造函數2面試
就和css中的繼承同樣,若是自身沒有定義就會繼承父元素的樣式。segmentfault
function a(){}; a.prototype.name = "追夢子"; var b = new a(); console.log(b.name); //追夢子
做用域 是針對變量的,好比咱們建立了一個函數,函數裏面又包含了一個函數,那麼如今就有三個做用域數組
全局做用域==>函數1做用域==>函數2做用域app
做用域的特色:先在本身的變量範圍中查找,若是找不到,就會沿着做用域往上找。函數
如:this
var a = 1; function b(){ var a = 2; function c(){ var a = 3; console.log(a); } c(); } b();
最後打印出來的是3,由於執行函數c()的時候它在本身的範圍內找到了變量a因此就不會越上繼續查找,若是在函數c()中沒有找到則會繼續向上找,一直會找到全局變量a,這個查找的過程就叫做用域鏈。spa
不知道你有沒有疑問,函數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中的自由變量以及做用域的進階