欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JS面試必備之手寫call/apply/bind/new

 更新時間:2023年05月18日 08:59:02   作者:feelingHy  
在JavaScript中,call、apply、bind、new是Function對象自帶的三個方法,也是面試時??嫉闹R點,所以本文就來和大家講講如何手寫實現(xiàn)這四個方法吧

一、Function.prototype.call()

call() 方法用于指定一個 this 值和單獨給出的一個或多個參數(shù)來調用一個函數(shù)。

該方法的語法和作用與 apply() 類似,只有一個區(qū)別,就是 call() 方法接受一個參數(shù)列表,而 apply() 方法接受的是一個包含多個參數(shù)的數(shù)組。

語法:

function.call(thisArg, arg1, arg2, ...)

參數(shù):

  • thisArg: 可選的。在 function 函數(shù)運行時使用的 this值。請注意,this 可能不是該方法看到的實際值:如果這個函數(shù)在非嚴格模式下,則指定為 null 或
  • undefined 時會自動替換為指向全局對象,原始值會被包裝。
  • arg1,arg2,...: 指定的參數(shù)列表。

返回值:

使用調用者提供的的 this 值和參數(shù)調用該函數(shù)的返回值。若該方法沒有返回值,則返回 undefined。

描述:

call() 允許為不同的對象分配和調用屬于一個對象的函數(shù)/方法。

使用:

var foo = {
    value: 1
};
function bar() {
    console.log(this.value);
}
bar.call(foo); // 1

這里需要注意兩點:

  • call 函數(shù)改變了 this 的指向,指向了 foo;
  • bar 函數(shù)執(zhí)行了;

那我們如何模擬實現(xiàn)呢?我們把代碼改造如下:

var foo = {
  value: 1,
  bar() {
    console.log(this.value);
  }
};
foo.bar(); // 1

這個時候 this 指向了 foo,但是卻給 foo 對象添加了一個屬性,只要思想不滑坡,方法總比困難多,我們用 delete 把它再刪除不就好了嗎?

1. 把函數(shù)設置為對象的屬性;

2. 執(zhí)行函數(shù);

3. 從對象中刪除函數(shù);

代碼如下:

Function.prototype.call3 = function(context) {
  // 第一步
  context.fn = this;
  // 第二步
  context.fn();
  // 第三步
  delete context.fn;
}

測試代碼:

var foo = {
  value: 1
};
???????function bar() {
  console.log(this.value);
}
bar.call3(foo); // 1

第一步:這里的 context 為 foo,this 為 函數(shù) bar,把 bar 函數(shù)賦值為 foo 的一個對象屬性,即:foo.fn = bar;

第二步:執(zhí)行 foo.fn 函數(shù);

第三步:刪除 foo.fn 函數(shù);

當然,我們也可以給 call 函數(shù)傳遞參數(shù),函數(shù)也可以有返回值,如下:

var value = 'global'; // a
var foo = {
  value: 1
};
function bar(name,age) {
  console.log(this.value); // b
  console.log(name);
  console.log(age);
  return {
      age,
      name
  };
}
bar.call(this, 'tom', 20); // 1 tom 20
var result = bar.call(null, 'tom', 20); // global tom 20  // c
console.log(result); // {age: 20, name: "tom"}

this 參數(shù)可以傳 null,當為 null 的時候,視為指向 window;

函數(shù)可以有返回值;

注意:a 處變量的聲明要用 var 而不是 let,否則在 c 處調用時,this 為 null 時,b 處的輸出結果會是 undefined,這里主要是在全局聲明時, 與 var 關鍵字不同,使用 let 在全局作用域中聲明的變量不會成為 window 對象的屬性(var 聲明的則會)。

實現(xiàn)代碼如下:

Function.prototype.call3 = function(context) {
  context = context || window;
  let args = [...arguments].slice(1);
  // 第一步
  context.fn = this;
  // 第二步
  let result = context.fn(...args);
  // 第三步
  delete context.fn;
  return result;
}

測試代碼如下:

Function.prototype.call3 = function(context) {
  context = context || window;
  let args = [...arguments].slice(1);
  // 第一步
  context.fn = this;
  // 第二步
  let result = context.fn(...args);
  // 第三步
  delete context.fn;
  return result;
}

二、Function.prototype.apply()

apply() 方法抵用一個具有給定 this 值的函數(shù),以及一個數(shù)組(或一個類數(shù)組對象)的形式提供的參數(shù)。

語法:

pply(thisArg)
apply(thisArg, argsArray)

參數(shù):

  • thisArg: 在 func 函數(shù)運行時使用的 this 值,請注意,this 可能不是該方法看到的實際值:如果這個函數(shù)在非嚴格模式下,則指定為 null 或 undefined 時會自動替換為指向全局對象,原始值會被包裝。
  • argsArray: 可選的,一個數(shù)組或者類數(shù)組對象,其中的數(shù)組元素將作為單獨的參數(shù)傳給 func 函數(shù)。如果該參數(shù)的值為null 或者 undefined,則表示不需要傳遞任何參數(shù)。

返回值:

調用有指定 this 值和參數(shù)的函數(shù)的結果。

描述:

apply 與 call()非常相似,不同之處在于提供參數(shù)的方式。apply 使用參數(shù)數(shù)組而不是一組參數(shù)列表。apply 可以使用數(shù)組字面量(array literal),如 fun.apply(this, ['eat', 'bananas']),或數(shù)組對象,如 fun.apply(this, new Array('eat', 'bananas'))。

你也可以使用 arguments 對象作為 argsArray 參數(shù)。arguments 是一個函數(shù)的局部變量。它可以被用作被調用對象的所有未指定的參數(shù)。這樣,你在使用 apply 函數(shù)的時候就不需要知道被調用對象的所有參數(shù)。你可以使用 arguments 來把所有的參數(shù)傳遞給被調用對象。被調用對象接下來就負責處理這些參數(shù)。

備注:雖然這個函數(shù)的語法與 call() 幾乎相同,但根本區(qū)別在于,call() 接受一個參數(shù)列表,而 apply() 接受一個參數(shù)的單數(shù)組。

apply 的實現(xiàn)其實跟 call 差不多,話不多說,直接上代碼:

Function.prototype.apply2 = function(context) {
 context = context || window;
 let args = [...arguments][1]; // 注意,這里call 傳遞的參數(shù)是一個數(shù)組,直接取數(shù)組下標第二位就可以了
 context.fn = this;
 let result = context.fn(...args);
 delete context.fn;
 return result;
}

測試代碼:

var value = 'global';
var foo = {
 value: 1
};
function bar(name,age) {
 console.log(this.value);
 console.log(name);
 console.log(age);
 return {
   name,
   age,
 };
}
var result = bar.apply2(foo, ['tom', 20]); // 1 tom 20
var result2 = bar.apply2(null, ['tom', 20]); // global tom 20
console.log(result); // {age: 20, name: "tom"}

三、Function.prototype.bind()

bind()  方法創(chuàng)建一個新的函數(shù),在 bind() 被調用時,這個新函數(shù)的 this 被指定為 bind() 的第一個參數(shù),而其余參數(shù)將作為新函數(shù)的參數(shù),供調用時使用。

語法:

function.bind(thisArg[, arg1[, arg2[, ...]]])

參數(shù):

  • thisArg: 調用綁定函數(shù)時作為 this 參數(shù)傳遞給目標函數(shù)的值。如果使用 new 運算符構造綁定函數(shù),則忽略該值。當使用 bind 在 setTimeout 中創(chuàng)建一個函數(shù)(作為回調提供)時,作為 thisArg 傳遞的任何原始值都將轉換為 Object。如果 bind 函數(shù)的參數(shù)列表為空,或者 thisArg 是 null或  undefined,執(zhí)行作用域的 this 將被視為新函數(shù)的 thisArg。
  • arg1, arg2, ...: 當目標函數(shù)被調用時,被預置入綁定函數(shù)的參數(shù)列表中的參數(shù)。

返回值:

返回一個原函數(shù)的拷貝,并擁有指定的 this 值和初始參數(shù)。

描述:

bind() 函數(shù)會創(chuàng)建一個新的綁定函數(shù)(bound function,BF)。綁定函數(shù)是一個 exotic function object(怪異函數(shù)),它包裝了元函數(shù)對象。調用綁定函數(shù)通常會導致執(zhí)行包裝函數(shù)。綁定函數(shù)具有如下內部屬性:

  • [[BoundTargetFunction]]  - 包裝的函數(shù)對象。
  • [[BoundThis]]  - 在調用包裝函數(shù)時始終作為 this 值傳遞的值。
  • [[BoundArguments]]  - 列表,在對包裝函數(shù)做任何調用都會優(yōu)先用列表元素填充參數(shù)列表。
  • [[Call]]  - 執(zhí)行與此對象關聯(lián)的代碼。通過函數(shù)調用表達式調用。內部方法的參數(shù)是一個this值和一個包含通過調用表達式傳遞給函數(shù)的參數(shù)的列表。

綁定函數(shù)也可以使用 new 運算符構造,它會表現(xiàn)為目標函數(shù)已經(jīng)被構建完畢了似的。提供的 this 值會被忽略,但前置參數(shù)仍會提供給模擬函數(shù)。

使用一(創(chuàng)建綁定函數(shù)):

bind 最簡單的用法就是創(chuàng)建一個函數(shù),不論怎么調用,這個函數(shù)都有同樣的 this 值。JavaScript 新手經(jīng)常犯的一個錯誤就是將一個方法從對象中拿出來,然后再調用,期望方法中的 this 是原來的對象。如果不做特殊處理的話,一般會丟失原來的對象?;谶@個函數(shù),用原始的對象創(chuàng)建一個綁定函數(shù),巧妙的解決了這個問題。

this.x = 9;    // 在瀏覽器中,this 指向全局的 "window" 對象
var module = {
  x: 81,
  getX: function() { return this.x; }
};
module.getX(); // 81
var retrieveX = module.getX;
retrieveX();
// 返回 9 - 因為函數(shù)是在全局作用域中調用的
// 創(chuàng)建一個新函數(shù),把 'this' 綁定到 module 對象
// 新手可能會將全局變量 x 與 module 的屬性 x 混淆
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81

關于制定 this 的指向,我們可以使用 call 或者 apply 來實現(xiàn),關于 call 和 apply 的實現(xiàn),我們在上面已經(jīng)講述過了。

模擬實現(xiàn)一:

Function.prototype.bind1 = function(context) {
  let that = this;
  return function() {
    return that.apply(context);
  }
}
測試代碼:
this.x = 9;    // 在瀏覽器中,this 指向全局的 "window" 對象
var module = {
  x: 81,
  getX: function() {
    return this.x;
  }
};
var boundGetX = retrieveX.bind1(module);
var result = boundGetX(); // 81
console.log(result);

使用二(傳參數(shù)):

bind() 的另一個簡單的使用方法是使一個函數(shù)擁有預設的初始參數(shù),只要這些參數(shù)(如果有的話)作為 bind() 的參數(shù)寫在 this 的后面,當綁定函數(shù)被調用的時候,這些參數(shù)會被插入到目標函數(shù)的參數(shù)列表的開始位置,傳遞給綁定函數(shù),綁定函數(shù)的參數(shù)會跟在他們后面。

function list() {
  return Array.prototype.slice.call(arguments);
}
function addArguments(arg1, arg2) {
    return arg1 + arg2
}
var list1 = list(1, 2, 3); // [1, 2, 3]
var result1 = addArguments(1, 2); // 3
// 創(chuàng)建一個函數(shù),它擁有預設參數(shù)列表。
var leadingThirtysevenList = list.bind(null, 37);
// 創(chuàng)建一個函數(shù),它擁有預設的第一個參數(shù)
var addThirtySeven = addArguments.bind(null, 37);
var list2 = leadingThirtysevenList();
// [37]
var list3 = leadingThirtysevenList(1, 2, 3);
// [37, 1, 2, 3]
var result2 = addThirtySeven(5);
// 37 + 5 = 42
var result3 = addThirtySeven(5, 10);
// 37 + 5 = 42,第二個參數(shù)被忽略

接下來我們來實現(xiàn)傳參的模擬:

Function.prototype.bind1 = function(context) {
  let that = this;
  // 獲取 bind1 函數(shù)從第二個到最后一個參數(shù)
  let args = [...arguments].slice(1);
  return function() {
     // 這個時候 arguments 是指 bind 返回的函數(shù)的入?yún)?
    return that.apply(context,[...args].concat([...arguments]));
  }
}

測試代碼:

var foo = {
  value: 1
};
function bar(name, age) {
  console.log(this.value);
  console.log(name);
  console.log(age);
  return this.value;
}
var bindFoo = bar.bind1(foo,'daisy');
bindFoo('18'); // 1

使用三(new 構造函數(shù))

bind 還有一個特點就是,一個綁定函數(shù)也能使用 new 操作符創(chuàng)建對象,這種行為就像把原函數(shù)當做構造器,提供的 this 將被忽略,同時調用時的參數(shù)被提供給模擬函數(shù)。

var value = 2;
var foo = {
    value: 1
};
function bar(name, age) {
    this.habit = 'shopping';
    console.log(this.value);
    console.log(name);
    console.log(age);
}
bar.prototype.friend = 'kevin';
var bindFoo = bar.bind(foo, 'daisy');
var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin

盡管在全局和 foo 中都聲明了 value 值,最后依然返回了 undefined,說明綁定的 this 值失效了,在下面將會見到 new 的模擬實現(xiàn),就會知道這個時候 this 已經(jīng)指向了 obj。

下面我們來模擬實現(xiàn)一下:

Function.prototype.bind2 = function(context) {
  let that = this;
  // let args  = [...arguments].slice(1);
  let args  = Array.prototype.slice.call(arguments,1);
  var fBound =  function() {
    let bindArgs = Array.prototype.slice.call(arguments);
    // 當作為構造函數(shù)時,this 指向實例,此時 this instanceof fBound 為 true
    return that.apply(this instanceof fBound ? this : context,args.concat(bindArgs));
  };
  // 修改返回函數(shù)的 prototype 為綁定函數(shù)的 prototype,實例就可以繼承綁定函數(shù)中的原型
  fBound.prototype = this.prototype;
  return fBound;
}

測試代碼如下:

var value = 2;
var foo = {
    value: 1
};
function bar(name, age) {
    this.habit = 'shopping';
    console.log(this.value);
    console.log(name);
    console.log(age);
}
bar.prototype.friend = 'kevin';
var bindFoo = bar.bind2(foo, 'daisy');
var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);

上面的 fBound.prototype = this.prototype 有一個缺點,直接修改 fBound.prototype 的時候,也會修改 this.prototype,因為他們是引用同一個地址。

如下:

var value = 2;
var foo = {
    value: 1
};
function bar(name, age) {
    this.habit = 'shopping';
    console.log(this.value);
    console.log(name);
    console.log(age);
}
bar.prototype.friend = 'kevin';
var bindFoo = bar.bind2(foo, 'Jack'); // bind2
var obj = new bindFoo(20); // 返回正確
// undefined
// Jack
// 20
obj.habit; // 返回正確
// shopping
obj.friend; // 返回正確
// kevin
obj.__proto__.friend = "Kitty"; // 修改原型
bar.prototype.friend; // 返回錯誤,這里被修改了
// Kitty

解決方案就是使用一個空對象作為中介,把 fBound.prototype 賦值為空對象的實例。

var fNOP = function () {};            // 創(chuàng)建一個空對象
fNOP.prototype = this.prototype;     // 空對象的原型指向綁定函數(shù)的原型
fBound.prototype = new fNOP();        // 空對象的實例賦值給 fBound.prototype

最終實現(xiàn)效果如下:

Function.prototype.bind2 = function(context) {
  let that = this;
  // let args  = [...arguments].slice(1);
  let args  = Array.prototype.slice.call(arguments,1);
  var fNOP = function () {};
  var fBound =  function() {
    let bindArgs = Array.prototype.slice.call(arguments);
    // 當作為構造函數(shù)時,this 指向實例,此時 this instanceof fBound 為 true
    return that.apply(this instanceof fBound ? this : context,args.concat(bindArgs));
  };
  // 修改返回函數(shù)的 prototype 為綁定函數(shù)的 prototype,實例就可以繼承綁定函數(shù)中的原型
  fNOP.prototype = this.prototype;
  fBound.prototype = new fNOP();
  return fBound;
}

四、new 操作符

使用 new 操作符實例化一個類等于使用 new 調用其構造函數(shù)。唯一可感知的不同之處就是,JavaScript 解釋器知道使用 new 和類意味著該使用 construct 函數(shù)進行實例化。

使用 new 調用類的構造函數(shù)會執(zhí)行如下操作:

  • 在內存中創(chuàng)建一個新的對象;
  • 改變新對象 proto 指向 構造函數(shù)的 prototype 屬性;
  • 構造函數(shù)內部的 this 被賦值為這個新對象;
  • 執(zhí)行構造函數(shù)內部的代碼;
  • 如果構造函數(shù)返回 非空對象,則返回該對象,否則,返回剛才創(chuàng)建的新對象。

new 的實現(xiàn)一:

function newCreate() {
  let obj = new Object();
  Constructor = Array.prototype.shift.call(arguments);
  obj.__proto__ = Constructor.prototype;
  Constructor.apply(obj, arguments);
  return obj;
}

在這里我們做了以下事情:

  • 創(chuàng)建一個新的對象;
  • 取出第一個參數(shù),這就是我們要傳入的構造函數(shù),因為 shift 會修改原數(shù)組,所以 arguments 會被去除第一個參數(shù);
  • 將 obj 的 proto 指向構造函數(shù)的 prototype;,這樣 obj 就可以訪問到構造函數(shù)原型中的屬性;
  • 使用 apply 改變構造函數(shù) this 的指向,這樣 obj 就可以訪問到構造函數(shù)中的屬性;
  • 返回 obj;

測試代碼如下:

function Person(name,age) {
  this.name = name;
  this.age = age;
}
Person.prototype.printName = function() {
  console.log(this.name);
}

但是我們還需要注意,如果構造函數(shù)返回非空對象,則返回該對象,否則,返回剛才創(chuàng)建的新對象。那么這句話怎么理解呢?請看下面的示例:

function Person(name,age) {
  this.name = name;
  this.age = age;
  return {
    name,
    gender: 'male',
  }
}
Person.prototype.printName = function() {
  console.log(this.name);
}
let p = new Person('tom', 18);
console.log(p.name); // tom
console.log(p.age); // undefined

在這個例子中,構造函數(shù)返回了一個對象,在實例 p 中就只能訪問到返回的對象中的屬性,也就是說只能訪問到 name 和 gender, age 屬性是訪問不到的。注意,這里返回的是一個對象,如果返回的是一個基本類型數(shù)據(jù)呢?

function Person(name,age) {
  this.name = name;
  this.age = age;
  return 20;
}
Person.prototype.printName = function() {
  console.log(this.name);
}
let p = new Person('tom', 18);
console.log(p.name); // tom
console.log(p.age); // 20

在這里返回的是一個基本類型的數(shù)據(jù),盡管有返回值,相當于沒有返回。

所以,下面我們需要判斷一下構造函數(shù)的返回值是基本類型數(shù)據(jù)還是一個一個對象。如果是一個對象,我們就返回這個對象,如果沒有,我們就不做任何處理。

function newCreate() {
  let obj = new Object();
  Constructor = Array.prototype.shift.call(arguments);
  obj.__proto__ = Constructor.prototype;
  let  result = Constructor.apply(obj, arguments);
  return typeof result === 'object' ? result : obj;
}

測試代碼:

返回一個基本類型數(shù)據(jù):

function Person(name,age) {
  this.name = name;
  this.age = age;
  return 20;
}
let p1 = newCreate(Person,'rose',20);
console.log(p1.name); // rose
console.log(p1.age); // 20
console.log(p1.gender); // undefined

返回一個對象:

function Person(name,age) {
  this.name = name;
  this.age = age;
  return {
    name,
    gender: 'male',
  }
}
let p1 = newCreate(Person,'rose',20);
console.log(p1.name); // rose
console.log(p1.age); // undefined
console.log(p1.gender); // male

以上就是JS面試必備之手寫call/apply/bind/new的詳細內容,更多關于JS手寫call apply bind new的資料請關注腳本之家其它相關文章!

相關文章

  • 微信小程序頁面返回傳值的4種解決方案匯總

    微信小程序頁面返回傳值的4種解決方案匯總

    這篇文章主要給大家介紹了關于微信小程序頁面返回傳值的4種解決方案,小程序開發(fā)中經(jīng)常會遇到這種場景,比如提交問題,然后需要返回之前頁面,本文通過示例代碼介紹的非常詳細,需要的朋友可以參考下
    2021-07-07
  • 原生JavaScript實現(xiàn)todolist功能

    原生JavaScript實現(xiàn)todolist功能

    本篇文章給大家介紹了通過原生JavaScript實現(xiàn)todolist功能相關知識點,對此有需要的朋友可以學習下。
    2018-03-03
  • 收集json解析的四種方法分享

    收集json解析的四種方法分享

    這篇文章主要介紹了json解析的四種方法,有需要的朋友可以參考一下
    2014-01-01
  • JavaScript深拷貝與淺拷貝原理深入探究

    JavaScript深拷貝與淺拷貝原理深入探究

    深拷貝和淺拷貝是面試中經(jīng)常出現(xiàn)的,主要考察對基本類型和引用類型的理解深度,這篇文章主要給大家介紹了關于js深拷貝和淺拷貝的相關資料,需要的朋友可以參考下
    2022-10-10
  • JavaScript采用遞歸算法計算階乘實例

    JavaScript采用遞歸算法計算階乘實例

    這篇文章主要介紹了JavaScript采用遞歸算法計算階乘,簡單分析了javascript遞歸算法的相關使用技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-08-08
  • 沒有document.getElementByName方法

    沒有document.getElementByName方法

    document.getElementByName方法沒有document.getElementsByName得到的是標簽的數(shù)組,下面為大家詳細介紹下具體的使用,感興趣的朋友可以參考下,希望對大家有所幫助
    2013-08-08
  • javascript自定義的addClass()方法

    javascript自定義的addClass()方法

    這篇文章主要介紹了javascript自定義的addClass()方法,通過傳參就可以實現(xiàn)樣式的添加
    2014-05-05
  • 原生js實現(xiàn)電商側邊導航效果

    原生js實現(xiàn)電商側邊導航效果

    本文主要分享了原生js實現(xiàn)電商側邊導航效果的示例代碼以及原理分析。具有很好的參考價值,下面跟著小編一起來看下吧
    2017-01-01
  • js獲取TreeView控件選中節(jié)點的Text和Value值的方法

    js獲取TreeView控件選中節(jié)點的Text和Value值的方法

    在實際項目中,遇到一個問題,首先彈出一個新窗口,新窗口中放了一個TreeView控件,現(xiàn)在要解決的是,如何單擊TreeView中一個節(jié)點,返回Text和Value到父頁面并關閉該新窗口,本文將詳細介紹此方法的實現(xiàn)
    2012-11-11
  • 使用Javascript接收get傳遞的值的代碼

    使用Javascript接收get傳遞的值的代碼

    用js獲取頁面值得仿佛,需要的朋友可以參考下。
    2011-11-11

最新評論