梁先国SEO专注网站优化,让您网站更具价值!

当前位置:首页 > WEB前端学习 > 教你如何理解this及call,apply和bind的用法

教你如何理解this及call,apply和bind的用法

时间:2018-08-18 23:24 来源:重庆网站制作公司(www.seozol.cn) 作者:重庆网站建设公司

教你如何理解this及call,apply和bind的用法
 


教你如何理解this及call,apply和bind的用法
 
JavaScript中最容易被误解的一个方面是this关键字。在这篇文章中,将通过学习四个规则来确定此关键字引用的内容。隐式绑定,显式绑定,new绑定和window绑定。在介绍这些时,你还将学习一些其他令人困惑的JavaScript部分,例如.call,.apply,.bind和new关键字。
 
1   前言
 
   
在深入研究JavaScript中这个关键字的细节之前,我们先退一步想一想,为什么这个关键字存在于第一位。这个关键字允许你重用具有不同上下文的函数。换句话说,"this"关键字允许你在调用函数或方法时决定哪个对象应该是焦点。在此之后我们谈论的一切都将建立在这个想法之上。我们希望能够在不同的上下文中或在不同的对象中重用函数或方法。
 
我们要看的第一件事是如何判断此关键字引用的内容。 当你试图回答这个问题时,你需要问自己的第一个也是最重要的问题是“这个函数在哪里被调用?"。你可以通过查看调用this关键字的函数的位置来判断此关键字引用的内容的唯一方法。
 
为了用一个你已经熟悉的例子来证明这一点,比如我们有一个greet函数,它接受了一个alert消息。
 
function
 greet 
(
name
)
 
{
 
  alert
(`
Hello
,
 my name is $
{
name
}`)
 
}
 
如果我要问你确切的警告,你的回答是什么? 只给出函数定义,就不可能知道。 为了知道名字是什么,你必须看看greet的函数调用。
 
greet
(
'Tyler'
)
 
原理是完全相同的,找出这个关键字的引用,你甚至,就像你对函数的正常参数一样 - 它会根据函数的调用方式而改变。
 
现在我们知道为了弄清楚this关键字引用的内容,你必须查看函数定义,让我们在实际查看函数定义时建立四个规则来查找。 他们是:
 
隐式绑定
 
 
 
new绑定
 
window绑定
 
 
 
2   隐式绑定
 
 
 
   
请记住,这里的目标是能够使用this关键字查看函数定义并告诉它引用的内容。 执行此操作的第一个也是最常见的规则称为隐式绑定。 我想说绝大多数情况它会告诉你这个关键字引用了什么。
 
假设我们有一个看起来像这样的对象
 
const
 user 
=
 
{
 
  name
:
 
'Tyler'
,
 
  age
:
 
27
,
 
  greet
()
 
{
 
    alert
(`
Hello
,
 my name is $
{
this
.
name
}`)
 
  
}
 
}
 
现在,如果你要在user对象上调用greet方法,那么你可以使用点表示法。
 
user
.
greet
()
 
这将我们带到隐式绑定规则的主要关键点。 为了弄清楚this关键字引用的内容,首先,在调用函数时,请查看点的左侧。 如果存在“点”,请查看该点的左侧以查找此关键字引用的对象。
 
在上面的示例中,user对象是“点的左侧”,这意味着此关键字引用user对象。 因此,就像在greet方法中,JavaScript解释器将此更改为user。
 
greet
()
 
{
 
  
// alert(`Hello, my name is ${this.name}`)
 
  alert
(`
Hello
,
 my name is $
{
user
.
name
}`)
 
// Tyler
 
}
 
让我们来看一个类似但稍微更高级的例子。 现在,我们不仅要拥有名称,年龄和问候属性,还要为我们的user对象提供一个mother属性,该属性也有名称和greet属性。
 
const
 user 
=
 
{
 
  name
:
 
'Tyler'
,
 
  age
:
 
27
,
 
  greet
()
 
{
 
    alert
(`
Hello
,
 my name is $
{
this
.
name
}`)
 
  
},
 
  mother
:
 
{
 
    name
:
 
'Stacey'
,
 
    greet
()
 
{
 
      alert
(`
Hello
,
 my name is $
{
this
.
name
}`)
 
    
}
 
  
}
 
}
 
现在问题变成了,下面的每个调用会发出什么alert?
 
user
.
greet
()
 
user
.
mother
.
greet
()
 
每当我们试图弄清楚这个关键字引用的内容时,我们需要查看调用并看看“左边的点”是什么。 在第一次调用中,user位于点的左侧,这意味着这将引用user。 在第二次调用中,mother位于点的左侧,这意味着这将引用mother。
 
user
.
greet
()
 
// Tyler
 
user
.
mother
.
greet
()
 
// Stacey
 
如前所述,绝大多数会有一个“左边的点”的对象。 这就是为什么在弄清楚这个关键字引用的内容时应该采取的第一步是“向左看点”。 但是,如果没有点怎么办? 这将我们带入下一个规则。
 
 
 
3   显式绑定
 
 
 
   
现在,如果我们的greet函数不是user对象的方法,那么它就是它自己的独立函数。
 
function
 greet 
()
 
{
 
  alert
(`
Hello
,
 my name is $
{
this
.
name
}`)
 
}
 
 
const
 user 
=
 
{
 
  name
:
 
'Tyler'
,
 
  age
:
 
27
,
 
}
 
我们知道,为了告诉this关键字引用的内容,我们首先要查看函数的调用位置。 现在这提出了一个问题,我们如何调用greet但是使用this关键字引用user对象来调用它。 我们不能像之前那样做user.greet()因为user没有greet方法。 在JavaScript中,每个函数都包含一个允许你完成此操作的方法,那就是call方法。
 
“call”是每个函数的一个方法,它允许你调用函数,指定调用函数的上下文。
 
考虑到这一点,我们可以使用以下代码在user的上下文中调用greet。
 
greet
.
call
(
user
)
 
同样,call是每个函数的属性,传递给它的第一个参数将是调用函数的上下文。 换句话说,传递给调用的第一个参数将是该函数中的this关键字引用的内容。
 
这是规则2(显式绑定)的基础,因为我们明确地(使用.call)指定this关键字引用的内容。
 
现在让我们稍微修改一下greet函数。 如果我们还想传递一些参数怎么办? 比如:
 
function
 greet 
(
lang1
,
 lang2
,
 lang3
)
 
{
 
  alert
(`
Hello
,
 my name is $
{
this
.
name
}
 and I know $
{
lang1
},
 $
{
lang2
},
 and $
{
lang3
}`)
 
}
 
现在,为了将参数传递给使用.call调用的函数,在指定第一个作为上下文的参数后,将它们逐个传递给它们。
 
function
 greet 
(
lang1
,
 lang2
,
 lang3
)
 
{
 
  alert
(`
Hello
,
 my name is $
{
this
.
name
}
 and I know $
{
lang1
},
 $
{
lang2
},
 and $
{
lang3
}`)
 
}
 
 
const
 user 
=
 
{
 
  name
:
 
'Tyler'
,
 
  age
:
 
27
,
 
}
 
 
const
 languages 
=
 
[
'JavaScript'
,
 
'Ruby'
,
 
'Python'
]
 
 
greet
.
call
(
user
,
 languages
[
0
],
 languages
[
1
],
 languages
[
2
])
 
它显示了如何将参数传递给使用.call调用的函数。 但是,正如你可能已经注意到的那样,必须从我们的语言数组中逐个传递参数,这有点令人讨厌。 如果我们可以将整个数组作为第二个参数传入并且JavaScript会将它们传播给我们,那将是很好的。 对我们来说这是个好消息,这正是.apply所做的。 .appall与.call完全相同,但不是逐个传入参数,而是传入一个数组,它会将这些数据作为函数中的参数传递出去。
 
所以现在使用.apply,我们的代码可以改为这个(下面),其他一切都保持不变。
 
const
 languages 
=
 
[
'JavaScript'
,
 
'Ruby'
,
 
'Python'
]
 
 
// greet.call(user, languages[0], languages[1], languages[2])
 
greet
.
apply
(
user
,
 languages
)
 
到目前为止,在我们的“显式绑定”规则下,我们已经了解了.call和.apply,它们都允许你调用一个函数,指定this关键字将在该函数内部引用的内容。 这条规则的最后一部分是.bind。 .bind与.call完全相同,但它不会立即调用该函数,而是返回一个可以在以后调用的新函数。 因此,如果我们使用.bind改变我们之前的代码,它看起来就像这样
 
function
 greet 
(
lang1
,
 lang2
,
 lang3
)
 
{
 
  alert
(`
Hello
,
 my name is $
{
this
.
name
}
 and I know $
{
lang1
},
 $
{
lang2
},
 and $
{
lang3
}`)
 
}
 
 
const
 user 
=
 
{
 
  name
:
 
'Tyler'
,
 
  age
:
 
27
,
 
}
 
 
const
 languages 
=
 
[
'JavaScript'
,
 
'Ruby'
,
 
'Python'
]
 
 
const
 newFn 
=
 greet
.
bind
(
user
,
 languages
[
0
],
 languages
[
1
],
 languages
[
2
])
 
newFn
()
 
// alerts "Hello, my name is Tyler and I know JavaScript, Ruby, and Python"
 
 
 
4   new绑定
 
 
 
   
确定this关键字引用内容的第三条规则称为new绑定。 如果你不熟悉JavaScript中的新关键字,那么每当你使用new关键字调用函数时,JavaScript解释器都会创建一个全新的对象并将其称为this对象。 因此,如果使用new调用函数,则this关键字引用解释器创建的新对象。
 
function
 
User
 
(
name
,
 age
)
 
{
 
  
/*
 
    Under the hood, JavaScript creates a new object called `this`
 
    which delegates to the User's prototype on failed lookups. If a
 
    function is called with the new keyword, then it's this new object
 
    that interpretor created that the this keyword is referencing.
 
  */
 
 
  
this
.
name 
=
 name
 
  
this
.
age 
=
 age
 
}
 
 
const
 me 
=
 
new
 
User
(
'Tyler'
,
 
27
)
 
 
 
5  window绑定
 
 
 
   
假设我们有以下代码
 
function
 sayAge 
()
 
{
 
  console
.
log
(`
My
 age is $
{
this
.
age
}`)
 
}
 
 
const
 user 
=
 
{
 
  name
:
 
'Tyler'
,
 
  age
:
 
27
 
}
 
如前所述,如果你想在user的上下文中调用sayAge,可以使用.call,.apply或.bind。 如果我们不使用任何这些,而只是像往常一样调用sayAge会发生什么
 
sayAge
()
 
// My age is undefined
 
你得到的是,毫无疑问的是undefined的,因为this.age将是未定义的。 这里的事情变得疯狂了。这里真正发生的是因为点的左边没有任何内容,我们没有使用.call,.apply,.bind或new关键字,JavaScript默认这是引用window对象。 这意味着如果我们将一个age属性添加到window对象,那么当我们再次调用我们的sayAge函数时,this.age将不再是未定义的,而是它将是window对象上的age属性。 不相信我? 运行此代码,
 
window
.
age 
=
 
27
 
function
 sayAge 
()
 
{
 
  console
.
log
(`
My
 age is $
{
this
.
age
}`)
 
}
 
非常粗糙,对吗? 这就是为什么第四个规则是window绑定。 如果没有满足其他规则,则JavaScript将默认this关键字引用window对象。
 
在ES5中添加的严格模式中,JavaScript将做正确的事情,而不是默认为window对象只是将“this”保持为未定义。
 
'use strict'
 
 
window
.
age 
=
 
27
 
 
function
 sayAge 
()
 
{
 
  console
.
log
(`
My
 age is $
{
this
.
age
}`)
 
}
 
 
sayAge
()
 
// TypeError: Cannot read property 'age' of undefined
 
 
6   总结
 
   
因此,将所有规则付诸实践,每当我在函数内部看到this关键字时,我们可以采用以下步骤弄清楚它所引用的内容。
 
1. 查看调用函数的位置
 
2. 点左边有一个对象吗? 如果是这样,那就是“this”关键字引用的内容。 如果没有,继续#3
 
3. 该函数是使用“call”,“apply”还是“bind”调用的? 如果是这样,它将明确说明“this”关键字引用的内容。 如果没有,继续#4
 
4. 是否使用“new”关键字调用了该函数? 如果是这样,“this”关键字引用由JavaScript解释器创建的新创建的对象。 如果没有,继续#5
 
5. 你是在“严格模式”吗? 如果是,则“this”关键字未定义。如果没有,继续#6
 
6. 是的,JavaScript很奇怪。 “this”引用了“window”对象。
 
 
本文标签:

版权声明:本文:教你如何理解this及call,apply和bind的用法 由重庆网站制作公司(www.seozol.cn)原创内容,如需要转载请注明原文网址:重庆网站建设公司http://www.seozol.cn/

 
喜欢看,就分享到:

围观: 9999次 | 责任编辑:重庆网站建设公司

推荐文章

热门文章

最新文章

回到顶部