JS 相容、繼承、bind、this

codeXiu發表於2018-12-14

這一篇文章主要是講述一些有關js的小知識點。因為我也不是很精通哪一方面只能把自己知道的一點點寫出來。取名大雜燴也是這意思吧,既然是道菜那麼就來嚐嚐我的手藝吧。

第一道菜

1.首先,我想說一下事件繫結。 事件繫結我們都知道有:on + 'type'的事件繫結還有addEventListener的事件繫結。

  • 在以前因為各種原因導致我們在不同的瀏覽器上面實現一樣的功能出現了相容的寫法,首先我們先來看看事件繫結的相容寫法。
    function bindEvent(obj, type, handle) {
    	if(window.addEventListener){
    		obj.addEventListener(type, handle, false);
    	}
    	if(window.attachEvent){ // IE
    		obj.attachEvent('on' + type, handle);
    	}
    	obj['on' + type] = handle;
    }
    複製程式碼
  • 事件解除除了on + 'type'其他的就是怎麼繫結怎麼解除。
    function delEvent(obj, type, handle) {
    	if(window.removeEventListener){
    		obj.removeEventListener(type, handle, false);
    	}
    	if(window.detachEvent){ // IE
    		obj.attachEvent('on' + type, handle);
    	}
    	obj['on' + type] = null;
    }
    複製程式碼
  • 順帶提一下IE與其它瀏覽器獲取target和event的相容方式。
    function handle(e){
    	let event = e || window.event; // IE
    	let target = e.target || e.srcElement;
    }
    複製程式碼
    下一篇我會詳細介紹DOM事件繫結和DOM事件等級。

第二道菜

2.這個知識點我們來說一下繼承。

  • 說到js的繼承,我是學後端的在C ++ 、Java裡面都跟js的繼承不一樣,所以一開始不好理解也覺得怪怪的。雖然ES6形式上有點像吧。那我們先來看看js一開始的繼承。
    function Father(){
    	this.a = 1;
    }
    Father.prototype.show = function () {
    	console.log(this.a);
    }
    function Son(){
    	Father.call(this);
    }
    let son = new Son();
    console.log(son.a);
    console.log(son.show());
    複製程式碼

JS 相容、繼承、bind、this
我們可以發現這種是拿不到父級原型上的函式的。

  • 我們再來看看第二種
    function Father(){
    	this.a = 1;
    }
    Father.prototype.show = function () {
    	console.log(this.a);
    }
    function Son(){
    	Father.call(this);
    }
    Son.prototype = Father.prototype;  //多了這一行
    let son = new Son();
    console.log(son.a);
    console.log(son.show());
    複製程式碼

JS 相容、繼承、bind、this
我們發現拿到了原型上的函式,但這樣就是最好的了嗎?我們一起來看看。當我們檢視Son的時候發現了一個奇怪的事。

JS 相容、繼承、bind、this
我們可以看到Son的constructor變成了Father,這是為什麼呢?因為constructor是原型上的函式,我們改變了Son的原型,因為Father的constructor是Father所以Son的constructor就變成了Father。而且這種方法我們改變Son.prototype時Father.prototype也會改變,那這說明我們的方法還是不夠完美。

  • 我們再來看看第三種
    function Father(){
    	this.a = 1;
    }
    Father.prototype.show = function () {
    	console.log(this.a);
    }
    function Son(){
    	Father.call(this);
    }
    function F(){};	//借用中間層來防止Son改變Father的原型
    F.prototype = Father.prototype;
    Son.prototype = new F();
    Son.prototype.constructor = Son; //改變Son的constructor
    let son = new Son();
    console.log(son.a);
    console.log(son.show());
    console.log(son.constructor);
    複製程式碼

JS 相容、繼承、bind、this
說到了建構函式那我們就看看什麼是建構函式。 function Father(){ this.a = 1; }這個是不是建構函式?如果說是那你就錯了,因為你思維固定了。我們都說建構函式開頭首字母大寫但那只是人為的規定並不是語法。還有如果有人問你this是誰,你可以放肆的告訴他,你沒呼叫我知道是誰啊。new只是一個操作符,任何函式都能通過new來執行,並不只是建構函式,只不過我們認為new之後的都是建構函式。

  • 那你知道new的時候發生了什麼嗎?我們一般都說四步走
    • 首先建立一個新的物件。
    • 改變這個物件的this
    • 改變這個物件的原型
    • 返回這個物件 我們試著寫一寫
    function myNew(){
    	let obj = {};
    	let Constructor = arguments[0];
    	obj.__proto__ = Constructor.prototype;
    	Constructor.apply(obj, arguments);
    	return obj;
    }
    let a = myNew(Son);
    console.log(a);
    複製程式碼

JS 相容、繼承、bind、this
補充: 我們都知道ES6那麼他的繼承跟我們的繼承有什麼區別呢?我們來看一下ES6的繼承。

class Father {
 	constructor(){
 		this.a = 1;
 	}
 	show(){
 		console.log(this.a);
 	}
 }
 class Son extends Father {
 	constructor(){
 		super();
 	}
 }
 let son = new Son();
 console.log(son.a);
 console.log(son.show());
複製程式碼

JS 相容、繼承、bind、this
我們發現是一樣的。只不過是這種實現方式更加讓人容易理解並且接受尤其是對於習慣了C++/Java之類語言的人。 其實class這種實現方式只是一個‘語法糖’,當我們用typeof Son的時候我們發現他是一個function,其實它就是一個函式只不過更加語義化了,而且裡面定義的方法其實是定義在了它的原型上面,我們輸出一下Father看看。

JS 相容、繼承、bind、this

第三道菜

3.我們再來介紹一下call、bind、apply

  • 它們都是用來改變this的,只不過有一些小的差別。
    • call和apply除了傳參方式的不同外,沒有不同的地方。
    • bind返回一個函式,call、apply立即執行。

演示我覺得應該不用了吧,因為我不是在寫文件,那麼我們說一些什麼呢,就說說怎麼模擬實現吧。我這裡用的是ES6的語法,我只是覺得這樣寫比較簡單但總體思路不變。 下面以這個為例:

var a = 3;
var obj = {
	a: 1
}
function show(g) {
	console.log(this.a, g);
}
複製程式碼
  • call
Function.prototype.callh = function(context){
	let args = [...arguments].slice(1); //首先獲取到傳遞的引數
	context.fn = this; // 獲取當前的呼叫者,並新增一個方法
	context.fn(...args);    // 傳入引數
	delete context.fn;  //刪除新增的函式
}    
show.callh(obj, 2);
複製程式碼

JS 相容、繼承、bind、this

  • apply
Function.prototype.applyh = function(context){
	let args = arguments[1]; // 跟call一樣,只不過apply傳進來的是陣列,所以arguments[1]指的是後面的引數
	context.fn = this;
	context.fn(...args);
	delete context.fn; 
	show.applyh(obj, [2]);
}
複製程式碼

JS 相容、繼承、bind、this

  • bind(重點)
// 簡易版
Function.prototype.bindh = function(context){
	let args = [...arguments].slice(1);
	let that = this;
	return function (argument) { // bind返回一個函式
		let args2 = [...arguments].slice(0);
		that.call(context, ...args.concat(args2));
	}
}
show.bindh(obj)(5);
複製程式碼

JS 相容、繼承、bind、this
但上面bind的方式有一點錯誤。我們來看看js裡面的bind和我們的在new之後有什麼區別。
JS 相容、繼承、bind、this
上面是js的後面是模擬的。看到了吧。

因為原型的原因,我們來改進一下。

Function.prototype.bindh = function(context){
	let args = [...arguments].slice(1);
	let that = this;
	function bnd(){}
	let fn = function (argument) {
		let args2 = [...arguments].slice(0);
		return that.call(this instanceof fn ? this : context, ...args.concat(args2));
	}
	bnd.prototype = this.prototype;
	fn.prototype = new bnd();
	return fn;
}
複製程式碼

這樣就行了。

第四道菜

4.再來講一講this

  • this一直是我們困惑的問題,有時候能看懂但程式碼一多,呼叫一多,我們就蒙了,下面我來簡單介紹一下。
    1. 在預設情況下this指向window(非嚴格模式)
    2. 誰呼叫指向誰
    3. call.apply
    4. bind
    5. new

我想到的差不多就這幾種吧,你或許會發現其實是按this繫結的優先順序升序排序的。如果你看懂了bind的模擬實現也許會知道為什麼bind的優先順序會高於call、apply。我覺得弄清楚這些this應該不是多大的問題吧,來一段程式碼看看。

var a = 3;
var obj = {
	a: 1,
	fn(){
		console.log(this.a);
	}
	+
}
function show() {
	console.log(this.a);
}
show();  //3
obj.fn(); //1
show.call(obj); // 1
show.apply(obj); // 1
show.bind(obj)(); // 1
show.bind(window).call(obj); //3  bind優先順序高,跟繫結順序沒區別
複製程式碼

希望這些菜能滿足您的胃口,但願也能給您填飽一些肚子。我以後還會繼續努力提高自己的廚藝,希望嚐到這個菜的人都會喜歡。

相關文章