JavaScript中this指向问题

JavaScript中this指向问题

JavaScript中的this对于许多开发者来说是一个非常头痛的问题,但是this关键字却又是一个非常重要的语法点

为什么需要this?

在很多的编程语言中都有this这个关键字,但是JavaScript中的this和常见的那些拥有this关键字的语言是有很大区别的

JavaScript中的this是非常灵活的,无论是它出现的位置还是它代表的含义

  • 我们编写一个对象的案例,看看有无this的区别
/* 有this的情况 */
var obj = {
    name: 'jingzhi',
    eating: function() {
		console.log(this.name + '正在吃饭')
    }studying: function() {
        console.log(this.name + '正在学习')
    }
}
/* 无this的情况 */
var obj = {
    name: 'jingzhi',
    eating: function() {
        console.log(obj.name + '正在吃饭')
    }studying: function() {
		concole.log(obj.name + '正在学习')
    }
}

可以看出this提供了一种隐形的传递一个对象的引用,api可以设计的简洁并且灵活,但是其缺点是学习成本高

this到底指向什么?

函数中的this到底指向什么,在函数的定义阶段我们是不能完全确定的,只有在函数调用的时候我们才能确定

看如下案例:定义一个函数,我们采用3中不同的方式去对它进行调用,看看它的结果分别有什么不同

function foo() {
    console.log(this)
}

// 1、函数单独直接调用
foo() // window

// 2、将foo函数放在一个对象中,通过对象引用
var obj = {
	name: 'jingzhi',
    foo: foo
}
obj.foo() // obj

// 3、通过call/apply调用
foo.call('jingzhi') // String('jingzhi')

对于以上案例,我们可以得到什么启示呢?

  1. 函数在调用的时候,JavaScript会默认给this绑定一个值
  2. this的绑定和函数的定义位置是没有关系的
  3. this的绑定和调用方式、调用位置有关
  4. this是在函数调用时才绑定

this的绑定规则

在JavaScript中,this到底是有怎么样的绑定规则呢?

  • 规则一:默认绑定;
  • 规则二:隐式绑定;
  • 规则三:显式绑定;
  • 规则四:new绑定;

默认绑定

独立的函数调用就是默认绑定,我们可以理解为函数没有绑定在某个对象上进行调用

  • 我们通过几个案例看一些
function foo() {
	console.log(this)
}

foo() // window
function test1() {
	console.log('test1', this)
    test2()
}

function test2() {
    console.log('test2', this)
    test3()
}

function test3() {
    console.log('test3', this)
}

test1()

请添加图片描述

function foo(func) {
    func()
}

var obj = {
    name: 'jingzhi',
    fn: function() {
        console.log(this)
    }
}

foo(obj.fn) // window

隐式绑定

通过某个对象进行调用的就是隐式绑定,也就是他的调用位置是在某个对象发起的函数调

  • 我们通过几个案例看一下
function foo() {
    console.log(this)
}

var obj = {
	name: 'jingzhi',
    foo: foo
}

obj.foo() // obj
function foo() {
    console.log(this)
}

var obj1 = {
    name: 'obj1',
    foo: foo
}

var obj2 = {
    name: 'obj2',
    obj1: obj1
}

obj2.obj1.foo() // obj1
function foo() {
    console.log(this)
}

var obj1 = {
    name: 'obj1',
	foo: foo
}

var bar = obj1.foo;
bar() // window (隐式丢失: 在特定情况下会存在隐式绑定丢失的问题,最常见的就是作为参数传递以及变量赋值)
/* bar 在此时是独立调用的 */

显式绑定

显式绑定需要有前提条件:

  • 必须在调用的对象内部有一个对函数的引用
JavaScript所有的函数都可以使用callapply方法

callapply可以用来改变this的指向;callapply都是为了改变某个函数运行时的context,即上下文而存在的,换句话说,就是为了改变函数体内部this的指向

  • call 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数

  • apply方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数

  function foo() {
      console.log(this)
  }
  
  var obj = {
  	name: 'jingzhi',
      foo: foo
  }
  
  foo() // window
  obj.foo() // obj
  foo.call(obj) // obj
  foo.apply(obj) // obj

请添加图片描述

JavaScript函数总是显式的绑定到一个对象bind方法

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用

bind属于硬绑定,返回的函数的this指向无法再次通过bindapplycall修改

function foo() {
    console.log(this)
}

var obj = {
    name: 'jingzhi'
}

var bar = foo.bind(obj)

bar() // obj
callapplybind方法传入特殊值

callapplybind若是把undefiend, null,那么执行作用域的this会被视为window

var name = 'jingzhi'
function foo() {
    this.name = 'haha'
    console.log(this.name, this)
}

function bar() {
    console.log(this)
    foo.call(null)
    foo.apply(null)
   	foo.bind(null)()
}

var obj = {
    name: 'obj',
    bar: bar
}

foo.call(null)
foo.apply(null)
foo.bind(null)()
obj.bar.call(null)

QQ截图20210910103346

通过传入null将函数内部this绑定成了window对象,并且window.name也从jingzhi变成了haha,obj的bar函数也改变了this的指向

显式绑定是指我们通过callapply以及bind方法改变this的行为,相比隐式绑定,我们能清楚的感知 this 指向变化过程

内置函数的绑定

定时器
setTimeout(function() {
    console.log(this)
}, 1000) // window
DOM事件
<body>
    <button>click</button>
    <script>
    	var btn = docment.querySeelector('button')
        btn.addEventListener('click', function() {
            console.log(this)
        })
        /* <button>click</button> */
    </script>
</body>
数组的高阶函数
var nums = [1, 2, 3, 4, 5]
nums.forEach(function(item) {
    console.log(this) // window
})

new绑定

JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字

使用new关键字来调用函数是,会执行如下的操作

  1. 创建一个全新的对象
  2. 这个新函数会被执行prototype连接
  3. 这个新对象会绑定到函数调用的this上(this绑定在此时完成)
  4. 如果函数没有返回其他对象,表达式会返回这个新对象

这个过程我们称之为构造调用,我们来看个例子

function Person() {
    console.log(this)
}

var p = new Person() // Person

绑定规则优先级

this共有四条绑定规则,那如果同时出现多条规则的情况又该如何判断this绑定?

  1. 默认规则的优先级最低
  2. 显式绑定的优先级比隐式绑定高
  3. new绑定的优先级高于隐式绑定
  4. new绑定高于bind绑定(new绑定无法与callapply绑定比较,call/apply会直接执行函数)
function foo() {
    console.log(this)
}

var obj = {
    foo1: foo,
    foo2: foo.bind('jingzhi')
}

// 1.默认规则的优先级最低
foo() // window
obj.foo1() // obj

// 2.显式绑定的优先级比隐式绑定高
var fn = obj.foo1.bind('jingzhi')
fn() // String {'jingzhi'}

obj.foo2() // String {'jingzhi'}

// 3.new绑定的优先级高于隐式绑定
var o1 = new obj.foo1() // foo

// 4.new绑定高于bind绑定(new绑定无法与call、apply绑定比较)
var o2 = new obj.foo2() // foo

this规则之外

间接函数引用

创建一个函数的间接引用,这种情况使用默认绑定规则

function foo() {
    console.log(this)
}

var obj1 = {
    name: 'obj1',
    foo: foo
}

var obj2 = {
    name: 'obj2'
}

obj1.foo() // obj1
/*
 1. 赋值(obj2.foo = obj1.foo)的结果是foo函数
 2. foo函数被直接调用,相当于独立函数调用,就是默认绑定
*/
(obj2.foo = obj1.foo)() // window

ES6 箭头函数

ES6 中允许使用箭头函数的方式来定义一个函数,箭头函数表达式的语法比函数表达式更简洁,并且不绑定this,根据外层作用域来决定this绑定什么

setTimeout(() => {
    console.log(this)
}, 1000) // window

var obj = {
    foo: () => {
        console.log(this)
    },
    bak: () => {
        setTimeout(() => {
            console.log(this)
        }, 1000)
    },
    bar: function() {
        setTimeout(() => {
            console.log(this)
        }, 1000)
    }
}

/* 对象的大括号并不构成一个作用域 */
obj.foo() // window
obj.bak() // window
/* 外层包裹一个非箭头函数,有作用域 */
obj.bar() // obj

箭头函数并不绑定this对象,那么this引用就会从上层作用域中找到对应的this

本篇文章是作者对coderwhy(王红元老师)JavaScript高级语法课程中this绑定、优先级学习结束之后的总结,感谢coderwhy老师的认真、细致的讲解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值