整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

关于JavaScript中this的指向,你知晓几分?请速来围观!

、this是什么东东?

this是指包含它的函数作为方法被调用时所属的对象。这句话理解起来跟卵一样看不懂,但是如果你把它拆分开来变成这三句话后就好理解一点了。

1.包含它的函数

2.作为方法被调用时

3.所属的对象

二、this的绑定规则

1、默认绑定

var x = 0;
function num(){
 this.x = 1;
}
console.log(this.x);//0
num();
console.log(this.x);//1  

当没有使用let或者var时,声明的变量是全局变量,指向window,

在这一形态下,其函数内部的this是与全局作用域时一样,直接指向window,执行完num()后,更改了x的值,所以其形态 依然等价于window。

function foo(){
      console.log(this.a)
    }
    var a = 2;
    foo(); //2
    console.log(this.document === document); // true
    // 在浏览器中,全局对象为 window 对象:
    console.log(this === window); // true
    this.a = 3;
    console.log(window.a); // 3

this指向全局对象。在非严格模式中,当funcion被不带任何修饰的函数直接引用进行调用的,只能使用默认绑定,无法应用其他规则。

2、隐式绑定

先看两段例子

function foo(){
 console.log(this.a)
}
var obj = {
 a:2,
 foo:foo
}
obj.foo() //2

隐式绑定————调用位置使用obj上下文来引用函数,可以说函数被调用时obj对象“拥有”或者“包含”它,它就指向谁。

function foo(){
 console.log(this.a)
}
var obj2 = {
 a:42,
 foo:foo
}
var obj1 = {
   a:2,
   obj2:obj2,
   foo:foo
}
obj1.foo() //2
obj1.obj2.foo() //42

此时可以控制台查看obj1,obj2对象里究竟包含了什么。

当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。

对象属性引用链中只有最后一层在调用位置中起作用。

身为前端老司机,还是得分享些干货精品学习资料的,前端资料获取方式:

1.在你手机的右上角有【关注】选项,点击关注!

2.关注后,手机客户端点击我的主页面,右上角有私信,请私信回复:【学习】

已经设置好了关键词自动回复,所以回复的时候请注意关键词哟~

下面思考这一段会输出什么呢?

function foo(){
 console.log(this.a)
}
var obj = {
 a:2,
 foo:foo
}
var bar = obj.foo; //这里bar将引用foo函数本身,所以不带有函数对象的上下文,this会直接指向window
bar() //?

为什么没有隐式绑定?这种情况称为隐式丢失。

因为bar=obj.foo 而obj.foo指向foo 也就是bar = function foo(){console.log(this.a)} foo中的this指向window,在window中并没有对a进行定义,so,结果是undefined

接下来再看一段会是什么结果呢?(参数传递时的隐式赋值)

function foo(){
 console.log(this.a)
}
function doback(fn){
 fn()
}
var obj = {
 a:2,
 foo:foo
}
var a = 'global';
doback(obj.foo) //? 显然答案是global,但是为什么呢?请继续往下看!

隐式丢失--被隐式绑定的函数会丢失绑定对象然后应用默认绑定。

最后函数执行 doback(obj.foo)时,会进行参数传递,也就是 fn = obj.foo,就和上一个例子一样了。既this指向的是window。

3、显示绑定

function foo(){
 console.log(this.a)
}
var obj = {
 a:2
}
foo.call(obj) //2

显式绑定--第一个参数是一个对象,接着在调用函数时将其绑定到this,通过foo.call(obj),我们可以在调用foo时强制把他的this绑定到obj上。

function foo(){
 console.log(this.a)
}
var obj = {
 a:2
}
var a = '3333333';
var bar = function(){
 foo.call(obj)
}
bar() // 2
bar.call(window) //2

硬绑定后bar无论怎么调用,都不会影响foo函数的this绑定。

通过创建函数bar(),并在内部调用foo.call(obj),强制把foo的this绑定到obj上。硬绑定的bar之后无论如何调用函数,都只会在obj上调用foo。

我们来看一下他的应用:

function foo(num) {
 console.log( this.a, num);
 return this.a + num;
}
var obj = {
 a: 2
};
var bar = function() {
 return foo.call( obj, ...arguments ); // 将obj对象硬编码进去
   //return foo.apply( obj, arguments ); // 也可以使用apply将obj对象硬编码进去
};
var b = bar( 3 ); // 2 3
console.log( b ); // 5
function fn1(){
 console.log(1);
}
function fn2(){
 console.log(2);
}
fn1.call(fn2); //输出 1
 
fn1.call.call(fn2); //输出 2

4、new绑定

我们不去深入了解构造函数,但要知道new来调用函数,或者说发生构造函数调用时,执行了哪些

当代码 new foo(...) 执行时:

(1) 创建一个新对象,它继承自foo.prototype.

(2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);new foo 等同于 new foo(),只能用在不传递任何参数的情况。

(3) 执行构造函数中的代码(为这个新对象添加属性) ;

(4) 返回新对象, 那么这个对象会取代整个new出来的结果。如果构造函数没有返回对象,那么new出来的结果为步骤1创建的对象。

this 关键字作为 JavaScript 中自动定义的特殊标识符,是我们不得不去面对、了解的知识点,很多初学者对 this 关键字可能会有含糊不清的感觉,但其实稍微理一理, this 并不复杂、不混乱。

this 是什么?

概述中我们说了 this 是 JavaScript 中的一个特殊关键字,this 和执行上下文相关,当一个函数被调用时,会建立一个活动记录,也称为执行环境。这个记录包含函数是从何处(call-stack)被调用的,函数是如何被调用的,被传递了什么参数等信息。这个记录的属性之一,就是在函数执行期间将被使用的 this 引用。 但是本文中并不准备往深层次分析其到底是什么,而是说明在应用场景和浅层原理中分析 this 到底是什么。 在分析之前,我们先来看一看当初的我们为什么会用 this。

为什么用 this?

以下是一段代码实例,其中用到了 this。

let me = {
 name: 'seymoe'
}
function toUpperCase() {
 return this.name.toUpperCase()
}
toUpperCase.call(me) // 'SEYMOE'

当然以上代码也完全可以不用 this 而采用传参的形式实现。

let me = {
 name: 'seymoe'
}
function toUpperCase(person) {
 return person.name.toUpperCase()
}
toUpperCase(me) // 'SEYMOE'

在这里为什么用 this 而不用传参的形式,是因为 this 机制用更优雅的方式隐含的传递一个对象的引用,可以拥有更干净的 API 设计和简单复用。使用模式越复杂,通过明确参数传递执行环境和传递 this 执行环境相比,就越复杂,当然以上只是一个应用场景之一。

调用栈与调用点

this 不是编写时绑定,而是运行时绑定。它依赖于函数调用的上下文条件。this 绑定和函数声明的位置无关,反而和函数被调用的方式有关,被调用的这个位置就叫调用点。所以我们分析 this 是什么的时候就必须分析调用栈(使我们到达当前执行位置而被调用的所有方法的堆栈)和调用点。

function baz() {
 // 调用栈是‘baz’,调用点是全局作用域
 console.log('baz')
 bar() 2. // bar的调用点
}
function bar() {
 // 调用栈是‘baz - bar’,调用点位于baz的函数作用域内
 console.log('bar')
}
baz() // 1. baz的调用点

以上应该比较容易理解 baz 和 bar 函数相对应的调用点和调用栈。

重点!this 的指向规则

现在我们知道了调用点,而调用点决定了函数调用期间 this 指向哪里的种规则,所以排好队一个一个来分析吧~

1. 默认绑定

顾名思义,就是 this 没有其他规则适用时的默认规则,独立函数调用就是最常见的情况。

var a = 2
function foo() {
 console.log(this.a)
}
function bar() {
 foo()
}
foo() // 2
bar() // 2

foo 是一个直白的毫无修饰的函数引用调用,所以默认绑定了全局对象,当然如果是严格模式 "use strict" this 将会是 undefined。

注意: 虽然是基于调用点,但只要foo的内容没在严格模式下,那就默认绑定全局对象。

var a = 2
function foo() {
 console.log(this.a)
}
(function (){
 "use strict";
 foo() // 2
})()

2. 隐含绑定

调用点是否拥有一个环境对象,或(拥有者、容器对象)。

function foo() {
 console.log(this.a)
}
let obj = {
 a: 2,
 foo: foo
}
obj.foo() // 2

当一个方法引用存在一个环境对象,隐含规则为该对象应该被用于这个函数调用的this绑定。

隐含绑定的情况下,容易出现丢失的情况!当隐含绑定丢失了它的绑定,意味着它会回退到默认绑定,下面是例子:

var a = 3
function foo() {
 console.log(this.a)
}
let obj = {
 a: 2,
 foo: foo
}
let bar = obj.foo
bar() // 3
// 另一种微妙的情况
function doFoo(fn) {
 fn && fn()
}
doFoo(obj.foo) // 3

函数的参数传递只是一种隐含的赋值,fn是foo函数的一个引用,而调用fn则是毫无掩饰的调用一个函数,默认绑定规则

3. 明确绑定

隐含绑定需要我们改变对象自身包含一个函数的引用来使 this 隐含的绑定到这个对象上,默认绑定也是不确定的情况,但是很多时候我们希望能够明确的使一个函数调用时使用某个特定对象作为 this 绑定,而不在这个对象上放置一个函数引用属性。

这个时候,call 和 apply 就该上场了。

JavaScript 中几乎所有的函数都能访问这两个方法,这两个方法接收的第一个参数都是一个用于 this 的对象,之后用这个指定的 this 来调用函数,这种方式就叫明确绑定。

function foo() {
 console.log(this.a)
}
let obj = {
 a: 2
}
foo.call(obj) // 2

一种明确绑定的变种可以保证一个函数始终被obj调用,无论如何也不会改变,这种方式叫硬绑定,通过 bind 方法实现。

var obj = {
 a: 2
}
function foo(something) {
 console.log(this.a, something)
 return this.a + something
}
var bar = foo.bind(obj)
bar(' is a number.') // 2 ,'is a number.'

我们注意到采用 bind 方式进行硬绑定时,该方法返回一个函数,这和 call 和 apply 是有所区别的。

4. new 绑定

传统面向对象语言中,通过 new 操作符调用构造函数会生成一个类实例。在 JavaScript 中其实没有构造器、类的概念,new 调用的函数仅仅只是一个函数,只是被 new 调用时改变了行为。所以不存在构造器函数,只存在函数的构造器调用。

new 操作符调用时会创建一个全新对象,连接原型链,并将这个新创建的对象设置为函数调用的 this 绑定,(默认情况)自动返回这个全新对象。

function Foo(a) {
 this.a = a
}
let bar = new Foo(2)
console.log(bar.a) // 2

优先级顺序

以上的规则在适用时存在优先级,级别如下:

硬绑定 > new 绑定 > 明确绑定 > 隐含绑定 > 默认绑定

所以我们已经能够总结出判定 this 的一般流程了。

判定 this 一般流程

  • 如果是 new 调用,this 是新构建的对象;
  • call、apply 或bind ,this 是明确指定的对象;
  • 是用环境对象(或容器)调用的,this 是这个容器;
  • 默认绑定,严格模式为 undefined,否则是global(全局)对象。

箭头函数中的 this

单独将箭头函数中的 this 列出来是因为并不能因为 this 在箭头函数中就有特殊的指向,而是因为箭头函数不会像普通函数去使用 this, 箭头函数的 this 和外层的 this 保持一致。这种保持一致是强力的,无法通过 call、apply 或 bind来改变指向。

const obj = {
 a: () => {
 console.log(this)
 }
}
obj.a() // window
obj.a.bind({})() // window

测验

最后,下面这个简单的测验,并不是很绕很难的面试题,有兴趣不妨做做,评论区回复答案一起探讨一下~

var a = 1
var obj = {
 a: 2,
}
var bar
obj.foo = foo
bar = obj.foo
function foo() {
 var a = 3
 console.log(this.a)
}
foo() // 1. ???
;(function (a) {
 "use strict";
 foo() // 2. ???
 bar.bind(a) // 3. ???
})(this)
obj.foo() // 4. ???
obj.foo.call(this) // 5. ???
bar() // 6. ???
bar.apply(obj) // 7. ???
var b = new foo() // 8. ???
console.log(b.a) // 9. ???

玩转 JavaScript 系列

写作是一个学习的过程,尝试写这个系列也主要是为了巩固 JavaScript 基础,并尝试理解其中的一些知识点,以便能灵活运用。本篇同步发布在「端技」公众号,如果有错误或者不严谨的地方,请务必给予指正,十分感谢!

链接:https://juejin.im/post/5c9115ee6fb9a070f03cf309

作者:Seymoe

文转载于网络,侵权必删

在 JavaScript 中,this 是一个相对难懂的特殊变量。因为它随处可用,而不仅仅是面向对象的编程中。本文将解释 this 是如何工作的,以及它可能导致问题的地方,并在文章的给出最佳实践。

为了方便理解 this ,最好的方式是根据使用 this 的位置划分三种类型:

  • 在函数内部: this 是一个额外的隐含的参数。
  • 在函数外部(顶级作用域中): this 在浏览器中指向全局对象;在 Node.jS 中指向 模块(module) 的接口(exports)。
  • 在传递给 eval() 的字符串中: eval() 如果是被直接调用, this 指的是当前对象;如果是被间接调用,this 指的是全局对象。

我们来看看每个类别。

1.函数内部的 this

这是 this 最常用的使用场景,因为 JavaScript 中,以三种不同的角色代表了所有的可调用的结构形式:

  • 真正函数( this 在松散模式下是全局对象,严格模式下是 undefined )
  • 构造函数( this 指向刚创建的实例 )
  • 方法: (this 指向方法调用的接受对象 )

在函数中,this 可以理解为一个额外隐含的参数。

1.1 真正函数中的 this

在真正函数中,this 的值取决于函数所处的模式:

  • 松散模式(Sloppy mode): this 指向全局对象 (浏览器中就是 window )。

JavaScript 代码: