標籤:btn func 驗證 png boolean cal ora on() style
一、實際情境中抽象出的一個問題
下面this各指向什嗎?
var a = { b: function() { console.log(this); }, f: function() { var c = this.b; c(); } }; a.b(); a.f();
第一個this指向a,第二個this指向window。(做對了嗎)
二、JavaScript中變數複製的問題
變數拷貝分為值拷貝和參考型別資料拷貝
一個變數向另一個變數複製基本類型資料值時,另一個變數會在自己所佔的記憶體中儲存一份屬於自己的資料值。
一個變數向另一個變數複製參考型別資料值時,實質上複製的是一個指標變數,這個指標指向堆記憶體中的對象,複製之後這兩個變數指向同一個對象。
Es5中基礎資料型別 (Elementary Data Type)包括string、number、null、undefined、boolean
參考型別包括Array、RegExp、Date、Function等
( 基本封裝類型:String、Boolean、Number; 單體內建對象:Global、Math)
三、函數
建立一個函數的方式: 函式宣告、函數運算式、通過Function建構函式建立,它接受任意數量的參數,最後一個參數始終被看成是函數體。
(例如:new Function(“num1”, “num2”, “return num1+num2”))
函式宣告有聲明提升的過程,解析器在向執行環境中載入資料時會優先讀取函式宣告,保證它在執行任何代碼之前都可用,而函數運算式則是在解析器
執行到它所在的程式碼時才被解析執行。
var condition = false; console.log( afun(100) );//undefined if (condition) { function afun(num) { return num + 100; } } else { function afun(num) { return num + 200; } }
上面的代碼,函數調用之前還沒有做判斷,所以還沒有afun函數
var condition = false; if (condition) { function afun(num) { return num + 100; } } else { function afun(num) { return num + 200; } } console.log( afun(100) );//300
js中沒有塊級範圍的概念,上面的代碼可以正常調用
js中函數也是一個對象,函數名是一個指標,所以在將一個函數的函數名複製給另一個變數時,這個變數也指向了這個函數
if (condition) { var bfun = function(num) { return num + 100; } } else { var bfun = function(num) { return num + 200; } } var cfun = bfun; bfun = null;// console.log( bfun(100) );//報錯 console.log( cfun(200) );//400
驗證函式拷貝之後,被賦值的變數和賦值的變數指向的是同一個函數,函數運算式和函式宣告定義的函數在複製時是一樣的,複製之後兩個變數指向同一函數。
複製之後,添加新的bfun,這個新的bfun覆蓋原來的bfun,cfun同樣也改變了,說明被賦值的變數和賦值的變數指向的是同一個函數。
function bfun(num) { return num + 200; } var cfun = bfun;function bfun(num) { return num*1000;} ; console.log( bfun(100) );//100000 console.log( cfun(200) );//200000
但是當採用函數運算式的形式(在用函數運算式定義的bfun函數,bfun也是一個全域變數,這裡沒有用var定義變數,規範寫法應該加上var) 再次定義一個同名的函數時,
如下:
function bfun(num) { return num + 200; } var cfun = bfun; bfun = function(num) { return num*1000; } ; console.log( bfun(100) );//100000 console.log( cfun(200) );//300
函數運算式定義的函數沒有覆蓋函式宣告定義的同名函數,在JavaScript中函數也是一個對象,函數名是一個指向函數的的變數指標,函數名也是一個變數,
在JavaScript中有一條規則是,函式宣告會覆蓋和其函數名同名的變數的聲明,但是不會覆蓋同名的變數的賦值,不管它們定義的順序如何。在用函數運算式定
義的bfun函數中,bfun也是一個變數,只不過它被賦的值是一個匿名函數,一個變數的值可以是任何類型(string、number、boolean、null、undefined、一個
函數、一個複雜類型資料(對象)等)。
驗證:函式宣告會覆蓋和其函數名同名的變數的聲明,但是不會覆蓋同名變數的賦值
bfun = function(num) { return num*1000; } ; var cfun = bfun; function bfun(num) { return num + 200; } console.log( bfun(100) );//100000 console.log( cfun(200) );//200000
再看一段代碼,最後結果為多少?
bfun = function(num) {
return num*1000; } ; var cfun = bfun; bfun = function(num) { return num + 200;
} console.log( bfun(100) ); console.log( cfun(200) );
最後結果為300、2000,後面的bfun覆蓋了前面的bfun,在後面調用bfun時調用的是第二個,而cfun仍然為第一個bfun的值,why?
不是說函數是一個對象,函數名相當於一個指標,指向的是函數,在複製之後兩個變數會指向同一個函數嗎?
當bfun是採用函式宣告的形式定義的時候,後面函式宣告定義一個相同的同名函數之後,bfun改變,cfun會隨之改變。而這裡的bfun是採用函數運算式定義的函數,
又會有什麼不同?
我的理解是,函數運算式定義的函數是沒有函數名的,在複製的時候覆制的是一個具體值,這裡複製的就是一個匿名函數,而不是像函式宣告複製一樣複製的是一個
記憶體位址(指標),
函數運算式拷貝,就等同於基礎資料型別 (Elementary Data Type)變數拷貝,變數與變數之間各儲存了一份屬於自己的值,其中一個變數的值改變不會影響另外一個。
函數的記憶體配置(無論是函式宣告還是函數運算式定義的函數):
函數運算式與函式宣告不同的是,bfun再重新被賦值一個匿名函數之後,此時bfun指向一個新的對象(有點類似於原型上採用字面量的方式定義屬性和方法一樣,
此時指向的是一個新對象),cfun還是指向原來的匿名函數。也就是函式宣告方式定義的函數,定義同名的函數,同名函數會覆蓋原來的函數;而函數運算式定義
的函數不會被同名函數覆蓋,定義同名函數之後原來的函數仍然存在。
函數是一個對象,所以可以在函數名上定義屬性和方法:
bfun = function(num) { return num*1000; } ; var cfun = bfun; bfun.a = 7777; console.log(bfun.a);//777 bfun.b = function(num) { return num + 200;} console.log(typeof bfun);//function console.log(bfun.a);//777 console.log( bfun(100) );//10000 console.log( bfun.b(100) ); //300
驗證:函數運算式定義的函數不會被同名函數覆蓋,定義同名函數之後原來的函數仍然存在。
bfun = function(num) { return num*1000; } ; var cfun = bfun; bfun.a = 7777; console.log(bfun.a);//777 bfun = function(num) { return num + 200; } console.log(typeof bfun);//function console.log(bfun.a);//undefined console.log( bfun(100) );//300 console.log( cfun(200) );//200000 console.log(cfun.a);//777
函數是一個對象,所以可以在函數對象上添加屬性和方法,這樣看似乎對象不一定就是儲存在堆記憶體中了?函數運算式中的匿名函數這樣的函數對象就是存在棧中啊
函數名是一個指標,指向函數,函數是一個對象,存在堆記憶體中。
四、閉包
var a = { b: function() { console.log(this); }, f: function() { var c = this.b; c(); } }; a.b(); a.f();
通過上面的分析,c這裡和b指向的是同一個函數,但是為什麼this的指向不同?問題在於調用(直接調用b時this為a,在f中將b複製給c之後,在調用c,this指向window)
函數的方式不一樣,結果執行環境也不同。
這個例子實質上類似於(this===window):
var object = { _name : "my object", getNameFunc : function() { var that = this; var a = function() { console.log("value:"+this._name);//undefined }; a(); }}; console.log(object.getNameFunc());
同樣也類似於(this===window):
var object = { _name : "my object", getNameFunc : function() { return function() {// this._name = "yyy"; console.log(this);//window return this._name;//undefined }; } }; console.log(object.getNameFunc()());
上面三個例子都是在函數中建立了一個閉包函數
閉包: 一個有權訪問另一個函數中的變數的函數就是閉包,常見的閉包就是一個函數中包含另一個函數
函數沒有利用對象調用時,this指向window;以上 閉包中的this指向window,這又引申到this指向的問題。
五、this的指向問題 (見:https://www.zhihu.com/question/19636194)
調用函數的幾種方式以及當前this的指向問題:
直接通過函數名調用(無論是在哪裡調用),此時this指向window
通過對象點函數名的形式調用,this指向函數的直接調用者
new關鍵字調用建構函式,this指向new出來的這個對象
通過apply或call方法調用,this指向第一個參數中的對象,如果第一個參數為null,this指向window
還可以通過bind()方法來改變函數的範圍,它返回的是一個函數的執行個體,最終需要通過這個執行個體來調用函數(apply和call方法是直接調用函數)
通過apply和call方法用來改變函數的範圍,這是每個函數中都包含的兩個非繼承的方法。
function a(num, num2) { }
當通過函數名直接調用一個函數時a(1,2),這個相當於a.apply(null, arguments),第一個參數是為null,所以this指向window
var b = { function a(num, num2) { } }
當通過對象調用一個函數時b.a(1,2),這個相當於a.apply(b, arguments),第一個參數是為b,所以this指向b
當把調用函數的方法換寫成apply或call的形式就很好理解this的指向了
關於直接調用函數和通過new關鍵調用建構函式的區別,如下:
考察不同調用方式this的指向問題和變數提升的問題
var a=10;function test(){a=5;alert(a);alert(this.a);var a;alert(this.a);alert(a);}test(); new test();// 5 10 10 5//5 undefined undefined 5
test()方法,this指向window,test中用var定義了一個a,它將被提升到執行環境的最前端,a=5
new test(),此時test是個建構函式,this.a沒有被定義所以為undefined
總結:
上面的問題的實際情境如下:
實際項目中抽象出來的一部分,用來統一給dom元素添加事件的寫法,大大提升了代碼的可維護性
var util = { maps: { ‘click #btn‘: ‘clickBtn‘ }, stop: function() { console.log("stop...."); }, clickBtn: function(event) {var e = event;var target = e.target || e.srcElement;console.log(target.id); this.stop(); }, _scanEventsMap: function(maps, isOn) { var delegateEventSplitter = /^(\S+)\s*(.*)$/; var bind = isOn ? this._delegate.bind(this) : this._undelegate.bind(this);// this._delegate(‘click‘, "#btn", this["clickBtn"]);// bind(‘click‘, "#btn", "clickBtn"); for (var keys in maps) { if (maps.hasOwnProperty(keys)) { var matchs = keys.match(delegateEventSplitter); bind(matchs[1], matchs[2], maps[keys]); } } }, _delegate: function(name, selector, func) { var ele = document.querySelector(selector), that = this, func = this[func]; console.log(func); ele.addEventListener(name, function(event) { var e = event || window.event; func.apply(that, arguments); }, false); }, _undelegate: function(name, selector, func) { var ele = $(selector); ele.removeEventListener(name, func, false); }, _init: function() { this._scanEventsMap(this.maps, true); } } util._init();
this、apply/call、bind、閉包、函數、變數複製