做爲面試中面試官最寵愛的一個問題,在這裏進行一個詳細的介紹,你們重點要放在理解,而不是背。 寫的很差或不對的地方,請你們積極指出,好了,話很少說,咱們「圓規正轉」javascript
先說一下三者的區別
共同點就是修改this指向,不一樣點就是
1.call()和apply()是馬上執行的, 而bind()是返回了一個函數
2.call則能夠傳遞多個參數,第一個參數和apply同樣,是用來替換的對象,後邊是參數列表。
3.apply最多隻能有兩個參數——新this對象和一個數組argArray
複製代碼
Function.prototype.myCall = function(context) {
context.fn = this;
context.fn();
}
const obj = {
value: 'hdove'
}
function fn() {
console.log(this.value);
}
fn.myCall(obj); // hdove
複製代碼
function fn() {
return this.value;
}
console.log(fn.myCall(obj)); // undefined
複製代碼
Function.prototype.myCall = function(context) {
// 1.判斷有沒有傳入要綁定的對象,沒有默認是window,若是是基本類型的話經過Object()方法進行轉換(解決問題3)
var context = Object(context) || window;
/** 在指向的對象obj上新建一個fn屬性,值爲this,也就是fn() 至關於obj變成了 { value: 'hdove', fn: function fn() { console.log(this.value); } } */
context.fn = this;
// 2.保存返回值
let result = '';
// 3.取出傳遞的參數 第一個參數是this, 下面是三種截取除第一個參數以外剩餘參數的方法(解決問題1)
const args = [...arguments].slice(1);
//const args = Array.prototype.slice.call(arguments, 1);
//const args = Array.from(arguments).slice(1);
// 4.執行這個方法,並傳入參數 ...是es6的語法用來展開數組
result = context.fn(...args);
//5.刪除該屬性(解決問題4)
delete context.fn;
//6.返回 (解決問題2)
return result;
}
const obj = {
value: 'hdove'
}
function fn(name, age) {
return {
value: this.value,
name,
age
}
}
fn.myCall(obj, 'LJ', 25); // {value: "hdove", name: "LJ", age: 25}
複製代碼
Function.prototype.myApply = function(context, args) {
var context = Object(context) || window;
context.fn = this;
let result = '';
//4. 判斷有沒有傳入args
if(!args) {
result = context.fn();
}else {
result = context.fn(...args);
}
delete context.fn;
return result;
}
const obj = {
value: 'hdove'
}
function fn(name, age) {
return {
value: this.value,
name,
age
}
}
fn.myApply(obj, ['LJ', 25]); // {value: "hdove", name: "LJ", age: 25}
複製代碼
bind() 方法建立一個新的函數,在 bind() 被調用時,這個新函數的 this 被指定爲 bind() 的第一個參數,而其他參數將做爲新函數的參數,供調用時使用(MDN)
複製代碼
Function.prototype.myBind = function(context) {
const self = this;
return function() {
self.apply(context);
}
}
const obj = {
value: 'hdove'
}
function fn() {
console.log(this.value);
}
var bindFn = fn.myBind(obj);
bindFn(); // 'hdove;
複製代碼
相比於call、apply,我我的以爲bind的實現邏輯更加複雜,須要考慮的東西不少,在這裏分開進行優化。java
在這裏咱們須要進行一下判斷,判斷調用bind
的是否是一個函數,不是的話就要拋出錯誤。es6
Function.prototype.myBind = function(context) {
if (typeof this !== "function") {
throw new Error("不是一個函數");
}
const self = this;
return function() {
self.apply(context);
}
}
複製代碼
咱們看下面這段代碼web
Function.prototype.myBind = function(context) {
if (typeof this !== "function") {
throw new Error("不是一個函數");
}
const self = this;
return function() {
self.apply(context);
}
}
const obj = {
value: 'hdove'
}
function fn(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFn = fn.myBind(obj, LJ, 25);
bindFn(); // 'hdove' undefined undefined
複製代碼
很明顯,第一個優化的地方就是傳遞參數,咱們來改造下面試
Function.prototype.myBind = function(context) {
if (typeof this !== "function") {
throw new Error("不是一個函數");
}
const self = this;
// 第一個參數是this,截取掉
const args = [...arguments].slice(1);
return function() {
/**
這裏咱們其實便可以使用apply又可使用call來更改this的指向
使用apply的目的其實就是由於args是一個數組,更符合apply的條件
*/
return self.apply(context, args);
}
}
const obj = {
value: 'hdove'
}
function fn(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFn = fn.myBind(obj, 'LJ', 25);
bindFn(); // 'hdove' 'LJ' 25
複製代碼
想在看起來沒什麼問題,可是咱們這樣傳一下參數數組
var bindFn = fn.myBind(obj, 'LJ');
bindFn(25); // 'hdove' 'LJ' undefined
複製代碼
咱們發現後面傳遞的參數丟了,這裏就須要使用柯里化來解決這個問題bash
Function.prototype.myBind = function(context) {
if (typeof this !== "function") {
throw new Error("不是一個函數");
}
const self = this;
// 第一個參數是this,截取掉
const args1 = [...arguments].slice(1);
return function() {
// 獲取調用時傳入的參數
const args2 = [...arguments];
return self.apply(context, args1.concat(args2));
}
}
const obj = {
value: 'hdove'
}
function fn(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFn = fn.myBind(obj, 'LJ');
bindFn(25); // 'hdove' 'LJ' 25
複製代碼
其實bind
還具備一個特性就是 做爲構造函數使用的綁定函數
,意思就是這個綁定函數能夠當成構造函數使用,能夠調用new
操做符去建立一個實例,當咱們使用new
操做符以後,this
其實不是指向咱們指定的對象,而是指向new
出來的這個實例的構造函數,不過提供的參數列表仍然會插入到構造函數調用時的參數列表以前。咱們簡單實現一下。app
Function.prototype.myBind = function(context) {
if (typeof this !== "function") {
throw new Error("不是一個函數");
}
const self = this;
const args1 = [...arguments].slice(1);
const bindFn = function() {
const args2 = [...arguments];
/**
這裏咱們經過打印this,咱們能夠看出來。
當這個綁定函數被當作普通函數調用的時候,this實際上是指向window。
而當作構造函數使用的時候,倒是指向這個實例,因此this instanceof bindFn爲true,這個實例能夠獲取到fn()裏面的值。
咱們能夠再fn裏面添加一個屬性test.
若是按照以前的寫法 打印出來的是undefined,正好驗證了咱們上面所說的this指向的問題。
因此解決方法就是添加驗證,判斷當前this
若是 this instanceof bindFn 說明這是new出來的實例,指向這個實例, 不然指向context
*/
console.log(this);
return self.apply(this instanceof bindFn ? this : context, args1.concat(args2));
}
return bindFn;
}
const obj = {
value: 'hdove'
}
function fn(name, age) {
this.test = '我是測試數據';
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFn = fn.myBind(obj, 'LJ');
var newBind = new bindFn(25);
console.log(newBind.test); // undefined
複製代碼
咱們都知道每個構造函數,都會有一個原型對象(prototype),來添加額外的屬性。函數
function fn(name, age) {
this.test = '我是測試數據';
}
fn.prototype.pro = '原型數據';
var bindFn = fn.myBind(obj, 'LJ', 25);
var newBind = new bindFn();
console.log(bindObj.pro); // undefined
複製代碼
由於咱們沒有綁定原型,因此會出現undefined
,咱們簡單綁定一下post
Function.prototype.myBind = function(context) {
if (typeof this !== "function") {
throw new Error("不是一個函數");
}
const self = this;
const args1 = [...arguments].slice(1);
const bindFn = function() {
const args2 = [...arguments];
return self.apply(this instanceof bindFn ? this : context, args1.concat(args2));
}
// 綁定原型
bindFn.prototype = self.prototype;
return bindFn;
}
function fn(name, age) {
this.test = '我是測試數據';
}
fn.prototype.pro = '原型數據';
var bindFn = fn.myBind(obj, 'LJ', 25);
var newBind = new bindFn();
console.log(bindObj.pro); // "原型數據"
複製代碼
可是這樣會出現這樣一個問題
function fn(name, age) {
this.test = '我是測試數據';
}
fn.prototype.pro = '原型數據';
var bindFn = fn.myBind(obj, 'LJ');
var bindObj = new bindFn();
bindObj.__proto__.pro = '篡改原型數據';
console.log(bindObj.__proto__ === fn.prototype); // true
console.log(bindObj.pro); // "篡改原型數據"
console.log(fn.prototype.pro); // "篡改原型數據"
當咱們修改bindObj的原型的時候,fn的原型也一塊兒修改了
這實際上是由於 bindObj.__proto__ === fn.prototype
咱們在修改bindObj的同時也間接修改了fn
複製代碼
解決方法其實很簡單,建立一個新方法proFn()
,來進行原型綁定,也就是實現繼承的幾種方式中的原型式繼承,而後咱們把這個新方法的實例對象綁定到咱們的綁定函數的原型中
Function.prototype.myBind = function(context) {
if (typeof this !== "function") {
throw new Error("不是一個函數");
}
const self = this;
const args1 = [...arguments].slice(1);
const bindFn = function() {
const args2 = [...arguments];
return self.apply(this instanceof bindFn ? this : context, args1.concat(args2));
}
// 綁定原型
function proFn() {} //建立新方法
proFn.prototype = self.prototype; //繼承原型
bindFn.prototype = new proFn(); //綁定原型
return bindFn;
}
function fn(name, age) {
this.test = '我是測試數據';
}
fn.prototype.pro = '原型數據';
var bindFn = fn.myBind(obj, 'LJ', 25);
var newBind = new bindFn();
console.log(bindObj.__proto__ === fn.prototype); // false
console.log(bindObj.pro); // "篡改原型數據"
console.log(fn.prototype.pro); // "原型數據"
複製代碼
這些東西實際上是面試中比較容易考到的問題,你們不要想着去背,背下來實際上是沒什麼用處的,容易會被問倒,重點仍是在於理解,理解了也就能夠垂手可得的寫出來了。但願這篇文章會給你們帶來收穫,那怕是一點點。在這裏,提早給你們夥拜個早年,鼠年幸運,跳槽順利,漲薪順利,哈哈。