溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

編程語(yǔ)言中this、call、apply的用法分析

發(fā)布時(shí)間:2021-03-16 10:44:15 來(lái)源:億速云 閱讀:234 作者:小新 欄目:web開(kāi)發(fā)

小編給大家分享一下編程語(yǔ)言中this、call、apply的用法分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

前言+思考題

記得當(dāng)時(shí)找實(shí)習(xí)的時(shí)候,總是會(huì)在簡(jiǎn)歷上加上一句——熟悉Js,例如this指向、call、apply等…

而每次投遞簡(jiǎn)歷時(shí)我都會(huì)經(jīng)歷如下步驟

  • 面試前,去問(wèn)度娘——this指向可以分為哪幾種啊~、call和apply的區(qū)別是什么?底氣由0% 猛漲到了 50%;

  • 面試中,面試官隨便扔上來(lái)幾道題,我都可以“堅(jiān)定的”給出答案,結(jié)果總是不盡人意…

  • 面試后,我會(huì)羞愧的刪除掉簡(jiǎn)歷上的這一條。而再之后投遞簡(jiǎn)歷時(shí)我又再次加上了這一條…

思考題

下面幾道題是我在網(wǎng)上搜索出來(lái)的熱度較高的問(wèn)題,如果大佬們可以輕松的回答上,并有清晰的思路,不妨直接點(diǎn)個(gè)贊吧(畢竟也消耗了不少腦細(xì)胞),如果大佬們能在評(píng)論處指點(diǎn)一二,就更好了?。?!

填空題:

  • 執(zhí)行Javascript中的【 】函數(shù)會(huì)創(chuàng)建一個(gè)新函數(shù),新函數(shù)與被調(diào)函數(shù)具有相同的函數(shù)體,當(dāng)目標(biāo)函數(shù)被調(diào)用時(shí) this 值指向第一個(gè)參數(shù)。

問(wèn)答題:

  • 請(qǐng)你談一下改變函數(shù)內(nèi)部this指針的指向函數(shù)有哪幾種,他們的區(qū)別是什么?

  • this的指向可以分為哪幾種?

代碼分析題:

var name = 'window'var person1 = {
  name: 'person1',
  show1: function () {
    console.log(this.name)
  },
  show2: () => console.log(this.name),
  show3: function () {
    return function () {
      console.log(this.name)
    }
  },
  show4: function () {
    return () => console.log(this.name)
  }}var person2 = { name: 'person2' }person1.show1()person1.show1.call(person2)person1.show2()person1.show2.call(person2)person1.show3()()person1.show3().call(person2)person1.show3.call(person2)()person1.show4()()person1.show4().call(person2)person1.show4.call(person2)()

一、this的指向

百度、谷歌上輸入“this的指向”關(guān)鍵字,大幾千條文章肯定是有的,總不至于為了全方面、無(wú)死角的掌握它就要將所有的文章都看一遍吧?所以不如梳理出一個(gè)穩(wěn)固的框架,順著我們的思路來(lái)填充它。

思維導(dǎo)圖

編程語(yǔ)言中this、call、apply的用法分析

本節(jié)精華:
  • this 總是(非嚴(yán)格模式下)指向一個(gè)對(duì)象,而具體指向哪個(gè)對(duì)象是在運(yùn)行時(shí)基于函數(shù)的執(zhí)行環(huán)境動(dòng)態(tài)綁定的,而非函數(shù)被聲明時(shí)的環(huán)境;

  • 除了不常用的with和eval的情況,具體到實(shí)際應(yīng)用中,this指向大概可以分為四種:

    • 作為對(duì)象的方法調(diào)用;

    • 作為普通函數(shù)調(diào)用;

    • 構(gòu)造器調(diào)用;

    • call 或 apply調(diào)用;

    • 箭頭函數(shù)中,this指向函數(shù)上層作用域的this;

  • 構(gòu)造器普通函數(shù)的區(qū)別在于被調(diào)用的方式;

  • A,call(B) => 可以理解成在B的作用域內(nèi)調(diào)用了A方法;

分析

1、作為對(duì)象的方法調(diào)用

當(dāng)函數(shù)作為對(duì)象的方法被調(diào)用時(shí),this指向該對(duì)象

var obj = {
    a: 'yuguang',
    getName: function(){
        console.log(this === obj);
        console.log(this.a);
    }};obj.getName(); // true yuguang

2、作為普通函數(shù)調(diào)用

當(dāng)函數(shù)不作為對(duì)象的屬性被調(diào)用,而是以普通函數(shù)的方式,this總是指向全局對(duì)象(在瀏覽器中,通常是Window對(duì)象)

window.name = 'yuguang';var getName = function(){
    console.log(this.name);};getName(); // yuguang

或者下面這段迷惑性的代碼:

window.name = '老王'var obj = {
    name: 'yuguang',
    getName: function(){
        console.log(this.name);
    }};var getNew = obj.getName;getNew(); // 老王

而在ES5的嚴(yán)格模式下,this被規(guī)定為不會(huì)指向全局對(duì)象,而是undefined

3、構(gòu)造器調(diào)用

除了一些內(nèi)置函數(shù),大部分Js中的函數(shù)都可以成為構(gòu)造器,它們與普通函數(shù)沒(méi)什么不同

構(gòu)造器普通函數(shù)的區(qū)別在于被調(diào)用的方式
當(dāng)new運(yùn)算符調(diào)用函數(shù)時(shí),總是返回一個(gè)對(duì)象,this通常也指向這個(gè)對(duì)象

var MyClass = function(){
    this.name = 'yuguang';}var obj = new MyClass();obj.name; // yuguang

但是,如果顯式的返回了一個(gè)object對(duì)象,那么此次運(yùn)算結(jié)果最終會(huì)返回這個(gè)對(duì)象。

var MyClass = function () {
    this.name = 1;
    return {
        name: 2
    }}var myClass = new MyClass(); console.log('myClass:', myClass); // { name: 2}

只要構(gòu)造器不顯示的返回任何數(shù)據(jù),或者返回非對(duì)象類(lèi)型的數(shù)據(jù),就不會(huì)造成上述問(wèn)題。

4、call或apply調(diào)用

跟普通的函數(shù)調(diào)用相比,用call和apply可以動(dòng)態(tài)的改變函數(shù)的this

var obj1 = {
    name: 1,
    getName: function (num = '') {
        return this.name + num;
    }};var obj2 = {
    name: 2,};// 可以理解成在 obj2的作用域下調(diào)用了 obj1.getName()函數(shù)console.log(obj1.getName()); // 1console.log(obj1.getName.call(obj2, 2)); // 2 + 2 = 4console.log(obj1.getName.apply(obj2, [2])); // 2 + 2 = 4

5.箭頭函數(shù)

箭頭函數(shù)不會(huì)創(chuàng)建自己的this,它只會(huì)從自己的作用域鏈的上一層繼承this。

因此,在下面的代碼中,傳遞給setInterval的函數(shù)內(nèi)的this與封閉函數(shù)中的this值相同:

this.val = 2;var obj = {
    val: 1,
    getVal: () => {
        console.log(this.val);
    }}obj.getVal(); // 2

常見(jiàn)的坑

就像標(biāo)題一樣,有的時(shí)候this會(huì)指向undefined

情況一

var obj = {
    name: '1',
    getName: function (params) {
        console.log(this.name)
    }};obj.getName();var getName2 = obj.getName;getName2();

這個(gè)時(shí)候,getName2()作為普通函數(shù)被調(diào)用時(shí),this指向全局對(duì)象——window。

情況二

當(dāng)我們希望自己封裝Dom方法,來(lái)精簡(jiǎn)代碼時(shí):

var getDomById = function (id) {
    return document.getElementById(id);};getDomById('p1') //dom節(jié)點(diǎn)

那么我們看看這么寫(xiě)行不行?

var getDomById = document.getElementByIdgetDomById('p1') // Uncaught TypeError: Illegal invocation(非法調(diào)用)

這是因?yàn)?

  • 當(dāng)我們?nèi)フ{(diào)用document對(duì)象的方法時(shí),方法內(nèi)的this指向document。

  • 當(dāng)我們用getId應(yīng)用document內(nèi)的方法,再以普通函數(shù)的方式調(diào)用,函數(shù)內(nèi)容的this就指向了全局對(duì)象。

利用call和apply修正情況二

document.getElementById = (function (func) {
    return function(){
        return func.call(document, ...arguments)
    }})(document.getElementById) // 利用立即執(zhí)行函數(shù)將document保存在作用域中

二、call和apply

不要因?yàn)樗摹皬?qiáng)大”而對(duì)它產(chǎn)生抗拒,了解并熟悉它是我們必須要做的,共勉!

思維導(dǎo)圖

編程語(yǔ)言中this、call、apply的用法分析

1.call和apply區(qū)別

先來(lái)看區(qū)別,是因?yàn)樗鼈?strong>幾乎沒(méi)有區(qū)別,下文代碼實(shí)例call和apply都可以輕易的切換。

當(dāng)它們被設(shè)計(jì)出來(lái)時(shí)要做到的事情一摸一樣,唯一的區(qū)別就在于傳參的格式不一樣

  • apply接受兩個(gè)參數(shù)

    • 第一個(gè)參數(shù)指定了函數(shù)體內(nèi)this對(duì)象的指向

    • 第二個(gè)參數(shù)為一個(gè)帶下標(biāo)的參數(shù)集合(可以是數(shù)組或者類(lèi)數(shù)組)

  • call接受的參數(shù)不固定

    • 第一個(gè)參數(shù)指定了函數(shù)體內(nèi)this對(duì)象的指向

    • 第二個(gè)參數(shù)及以后為函數(shù)調(diào)用的參數(shù)

因?yàn)樵谒校ǚ羌^)函數(shù)中都可以通過(guò)arguments對(duì)象在函數(shù)中引用函數(shù)的參數(shù)。此對(duì)象包含傳遞給函數(shù)的每個(gè)參數(shù),它本身就是一個(gè)類(lèi)數(shù)組,我們apply在實(shí)際使用中更常見(jiàn)一些。

call是包裝在apply上面的語(yǔ)法糖,如果我們明確的知道參數(shù)數(shù)量,并且希望展示它們,可以使用call。

當(dāng)使用call或者apply的時(shí)候,如果我們傳入的第一個(gè)參數(shù)為null,函數(shù)體內(nèi)的this會(huì)默認(rèn)指向宿主對(duì)象,在瀏覽器中則是window。

借用其他對(duì)象的方法

我們可以直接傳null來(lái)代替任意對(duì)象

Math.max.apply(null, [1, 2, 3, 4, 5])
2.call和apply能做什么?

使用一個(gè)指定的 this 值和單獨(dú)給出的一個(gè)或多個(gè)參數(shù)來(lái)調(diào)用一個(gè)函數(shù)——來(lái)時(shí)MDN

  • 調(diào)用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)繼承;

  • 調(diào)用函數(shù)并且指定上下文的 this;

  • 調(diào)用函數(shù)并且不指定第一個(gè)參數(shù);

1.調(diào)用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)繼承

通過(guò)“借用”的方式來(lái)達(dá)到繼承的效果:

function Product(name, price) {
	this.name = name;
	this.price = price;}function Food(name, price) {
	Product.call(this, name, price); //
	this.category = food;}var hotDog = new Food('hotDog', 20);

2.調(diào)用函數(shù)并且指定上下文的 this

此時(shí)this被指向了obj

function showName() {
    console.log(this.id + ':' + this.name);};var obj = {
    id: 1,
    name: 'yuguang'};showName.call(obj)

3.使用call單純的調(diào)用某個(gè)函數(shù)

Math.max.apply(null, [1,2,3,10,4,5]); // 10

三、模擬實(shí)現(xiàn)一個(gè)call

先來(lái)看一下call幫我們需要做什么?

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

就像解方程,要在已知條件中尋找突破哦口:

  • call 使得this的指向變了,指向了foo;

  • show 函數(shù)被執(zhí)行了;

  • 傳入的參數(shù)應(yīng)為 this + 參數(shù)列表;

第一版代碼

上面提到的3點(diǎn),僅僅完成了一點(diǎn),且傳入的參數(shù)

var foo = {
    value: 1};function show() {
    console.log(this.value);};Function.prototype.setCall = function (obj) {
    console.log(this); // 此時(shí)this指向show
    obj.func = this; // 將函數(shù)變成對(duì)象的內(nèi)部屬性
    obj.func(obj.value); // 指定函數(shù)
    delete obj.func // 刪除函數(shù),當(dāng)做什么都沒(méi)發(fā)生~}show.setCall(foo);

第二版代碼

為了解決參數(shù)的問(wèn)題,我們要能獲取到參數(shù),并且正確的傳入:

var foo = {
    value: 1};function show(a, b) {
    console.log(this.value);
    console.log(a + b);};Function.prototype.setCall = function (obj) {
    obj.fn = this; // 將函數(shù)變成對(duì)象的內(nèi)部屬性
    var args = [];
    for(let i = 1; i < arguments.length; i++){
        args.push('arguments[' + i + ']');
    }
    eval('obj.fn(' + args + ')'); // 傳入?yún)?shù)
    delete obj.fn; // 刪除函數(shù),當(dāng)做什么都沒(méi)發(fā)生~}show.setCall(foo, 1, 2); // 1 3

此時(shí),我們就可以做到,傳入多個(gè)參數(shù)的情況下使用call了,但是如果你僅想用某個(gè)方法呢?

第三版代碼

Function.prototype.setCall = function (obj) {
    var obj = obj || window;
    obj.fn = this;
    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
    	args.push('arguments[' + i + ']');
  	}
  	var result = eval('obj.fn(' + args +')');
  	delete obj.fn;
  	return result;};// 測(cè)試一下var value = 2;var obj = { value: 1 };function bar(name, age) {
  	console.log(this.value);
  	return {
    	value: this.value,
    	name: name,
    	age: age  	}}bar.setCall(null); // 2console.log(bar.setCall(obj, 'yuguang', 18));

四、bind

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

提到了callapply,就繞不開(kāi)bind。我們?cè)囍鴣?lái)模擬一個(gè)bind方法,以便加深我們的認(rèn)識(shí):

Function.prototype.bind = function (obj) {
    var _this = this; // 保存調(diào)用bind的函數(shù)
    var obj = obj || window; // 確定被指向的this,如果obj為空,執(zhí)行作用域的this就需要頂上嘍
    return function(){
        return _this.apply(obj, arguments); // 修正this的指向
    }};var obj = {
    name: 1,
    getName: function(){
        console.log(this.name)
    }};var func = function(){
    console.log(this.name);}.bind(obj);func(); // 1

這樣看上去,返回一個(gè)原函數(shù)的拷貝,并擁有指定的 this 值,還是挺靠譜的哦~

以上是“編程語(yǔ)言中this、call、apply的用法分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI