【JS】this揭秘

this揭秘

bluesboneW发布于 今天 13:34

1 前置知识

1.1 对this的一个大误解

很多人对this有一个潜意识里的误解——认为this的值取决于其所在函数是在哪里声明的

let obj = {

a: function () {

console.log(this);

},

b: function () {

let f = obj.a;

f();

}

}

obj.b(); // window

很多人在遇到上面这个面试题时,看到函数是在对象内部声明,都会误认为this指向obj

1.2 函数名即指针

this作为一个函数内部的对象,自然与函数紧密相连

然而,很多朋友都没搞清楚函数名竟然是个指针

看下面的代码,思考问题:

  1. fn1是否能够直接找到其函数体的位置(ans:能)
  2. fn1和obj还有关系吗(ans:无关系)

function fn() {

console.log(this);

}

let obj = {

a() {

return fn;

}

}

let fn1 = obj.a();

fn1(); // window

如果上面两个问题你能想明白,或许你对this能够指向window已经有一定的感觉

2 this机制详解

2.1 this本质上就是指向它的调用者

javascript作为一门解释型语言,this的值到底是什么,必须到函数被调用时才能确定

什么意思呢,比如下面这段代码

function fn() {

console.log(this);

}

请问:你现在知道fn里的this是什么吗?

不可能知道的,因为fn没有被调用!

那么什么又叫做必须到函数被调用时才能确定呢?

我们来看看对上面的fn进行不同的调用,结果是什么

function fn() {

console.log(this);

}

let obj = {fn};

fn(); // window

obj.fn(); // obj

可以很明显地发现,因为调用方式的不同,this的值也不同

先别急,整个文章都是围绕这句话展开的

首先,我们分析一下一个函数到底有几种调用方式,再分别进行阐述:

  1. 全局调用
  2. 方法调用
  3. new调用

2.1 全局调用(独立调用)

function foo() {

console.log(this);

}

foo(); // window

对于foo()而言,foo是函数名,而在js中,函数名只是一个指针
类似这种函数名()形式,孤零零地单独出现,《你不知道的javascript》作者把这种方式称之为独立调用,而这种调用会使得被调用函数内部的this默认绑定为window

结合this本质上就是指向它的调用者这句话,全局调用的本质其实就是window调用了foo这个函数

foo();

// 等价于下面

window.foo();

2.2 方法调用

let obj = {

fn() {

console.log(this);

}

}

obj.fn(); // obj

2.2.1 方法调用的就近原则

let obj = {

a: {

b: {

fn() {

console.log(this);

}

}

}

}

obj.a.b.fn(); // obj.a.b

2.2.2 和全局调用进行对比

下面这段代码,在不运行的情况下,很多人都会猜错

let obj = {

a() {

console.log(this);

}

}

let fn = obj.a;

obj.a(); // obj

fn(); // window

相信大家对obj.a();没有疑问,关键在于fn()为什么是window

其实结合1.2小节 函数名即指针一起来看,fn只是一个指针,现在fn指向了obj.a这个函数,而fn()是一个全局调用,因此this自然指向了window

2.3 new

function fn() {

console.log(this);

}

new fn(); // 结果看下面的截图

【JS】this揭秘

3 其它场景下的this解惑

3.1 严格模式下的this

全局作用域里函数中的this是undefined

function test() {

"use strict"

console.log(this)

}

test() // undefined

所以,在使用构造函数时,如果忘了加new,this不再指向全局对象,而是报错,因为这就是函数的全局调用

let People = function (name) {

"use strict"

this.name = name

}

People() // Cannot set property 'name' of undefined

3.2 数组中的this

function fn() {

console.log(this)

}

arr[fn, fn2, fn3]

arr[0]() // ??

// answer:arr

// 解析

// 数组也是对象的一种

// arr[0]() 可以看做 arr.0().call(arr)

3.3 嵌套函数里的this

// 例子一

function fn0() {

function fn() {

console.log(this);

}

fn();

}

fn0(); // fn中this是全局变量

// 例子二

let a = {

b: function () {

console.log(this) // {b:fn}

function xx() {

console.log(this) // window

}

xx()

}

}

a.b()

3.4 setTimeout、setInterval中的this

document.addEventListener('click', function (e) {

console.log(this);

setTimeout(function () {

console.log(this); // 这里的this是全局变量

}, 200);

}, false);

3.5 事件中的this

document.querySelector('div').addEventListener('click',function (e) {

console.log(this) // <div></div>

})

4 自己指定this

4.1 call/apply和bind概览

  1. 我们要将call/apply归为一类,bind单独归为一类

  2. 三者的共同点是都可以指定this
  3. call/apply和bind都是绑定在Function的原型上的,所以Function的实例都可以调用这三个方法

Function.prototype.call(this,arg1,arg2)

Function.prototype.apply(this,[arg1,arg2])

Function.prototype.bind(this,arg1,arg2)

4.2 call/apply —— 第一个参数是this

4.2.1 call/apply的作用

  1. 调用函数

  2. 改变该函数的this指向
  3. 给函数传递参数

返回值
返回值是你调用的函数的返回值

window.a = 1

function print(b, c) {

console.log(this.a, b, c)

}

// 独立调用

print(2, 3) // 1 2 3

// 使用call和apply

print.call({a: -1}, -2, -3) // -1 -2 -3

print.apply({a: 0}, [-2, -3]) // 0 -2 -3

4.2.2 apply传递数组参数

let fn = function () {

console.log(arguments)

}

fn.apply(null, [1, 2, [3, 4]]);

【JS】this揭秘

例子一

Math.max()不接收数组的传递,因此如果想要找到一个长度很长的数组的最大值会非常麻烦

我们可以使用apply方法将数组传递给Math.max()

其本质还是将参数数组拆开再传递给Math.max()

let answer = Math.max.apply(null, [2, 4, 3])

console.log(answer) // 4

// 注意下面三个等价

Math.max.apply(null, [2, 4, 3])

Math.max.call(null, 2, 4, 3)

Math.max(2, 4, 3)

例子二:合并两个数组

非常值得注意的就是arr2数组被拆开了,成了一个一个的参数

// 将第二个数组融合进第一个数组

// 相当于 arr1.push('celery', 'beetroot');

let arr1 = ['parsnip', 'potato']

let arr2 = ['celery', 'beetroot']

arr1.push.apply(arr1, arr2)

// 注意!!!this的意思是要指定调用了push这个方法

// 所以当 this = arr1 后

// 就成了 arr1 调用了 push方法

// 上述表达式等价于 arr1.push('celery', 'beetroot')

console.log(arr1)

// ['parsnip', 'potato', 'celery', 'beetroot']

Math.max.apply(null, [2, 4, 3]) // 完美运行

arr1.push.apply(null, arr2) // 报错 Uncaught TypeError: Array.prototype.push called on null or undefined

// 说明

Math = {

max: function (values) {

// 没用到this值

}

}

Array.prototype.push = function (items) {

// this -> 调用push方法的数组本身

// this为null的话,就是向null里push,会报错

// Array.prototype.push called on null or undefined

}

// 下面三个值是完全等价的,因为this值已经是arr1

Array.prototype.push.apply(arr1, arr2)

arr1.push.apply(arr1, arr2)

arr2.push.apply(arr1, arr2)

4.2.3 小测试

function xx() {

console.log(this)

}

xx.call('1') // ??

xx() // ??

4.3 bind

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

4.3.1 作用

  1. 改变该函数的this指向
  2. 返回一个新函数

4.3.2 绑定函数与目标函数

函数使用bind()方法后会返回一个新函数【绑定函数】
而原函数为【目标函数】

那么新函数被调用时会发生什么呢?
下面一句话务必记住
其实就是把原函数call/apply一下,并指定你传递的this

function xx() {

console.log(this)

}

let foo = xx.bind({'name':'jason'})

// foo —— 新函数【绑定函数】

// xx —— 原函数【目标函数】

foo()

// 新函数调用时对原函数的操作如下(伪代码)

function foo(){

xx.call({'name':'jason'})

}

也就是说,实际上,在foo()这一句执行时做了如下事情:

1.给xx(原函数)指定this

2.调用xx(原函数)
一定要注意这两步是在新函数被调用时才发生,不调用不发生
你也可以总结为一句话:给原函数 call/apply 了一下

4.3.3 bind()传参

  1. bind(this,arg1,arg2...)会将arg1,arg2...插入到新函数【绑定函数】的arguments的开始位置

  2. 调用新函数时,再传递的参数也只会跟在arg1,arg2...后面

function list() {

// 原函数【目标函数】

return Array.prototype.slice.call(arguments);

}

// 新函数【绑定函数】

let leadingThirtysevenList = list.bind(null, 37, 38);

let newList1 = leadingThirtysevenList();

let newList2 = leadingThirtysevenList(1, 2, 3);

let newList3 = leadingThirtysevenList(-1, -2);

console.log(newList1) // [ 37, 38 ]

console.log(newList2) // [ 37, 38, 1, 2, 3 ]

console.log(newList3) // [ 37, 38, -1, -2 ]

4.3.4 使用 this + call/apply原生实现一个bind【重点】

  1. bind的第一个参数是this

  2. bind可以return一个新函数,这个新函数可以调用原函数并且可以指定其this,还可以接受参数
  3. 新函数传递的参数要在bind传递的参数的后面

Function.prototype._bind = function () {

// bind指定的this

let bindThis = arguments[0]

// bind传递的参数

let bindArgs = Array.prototype.slice.call(arguments, 1)

// this指向调用_bind的原函数,即旧函数

let oldFunction = this

// 返回的新函数

return function () {

// 所谓的新函数和旧函数,函数体一样,实际上只是用apply调用了旧函数,再return旧函数的返回值

// 截获调用新函数时传递的参数

let newArgs = Array.prototype.slice.call(arguments)

// 合并bindArgs和newArgs,并且newArgs要在bindArgs后面,再传递给旧函数

return oldFunction.apply(bindThis, bindArgs.concat(newArgs))

}

}

// 测试

function fn() {

console.log(arguments)

}

let newFn = fn._bind(null, 1, 2);

newFn(4, 6)

javascriptthis的用法

阅读 43发布于 今天 13:34

本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议


前端前进指南

Just For Fun

avatar

bluesboneW

今天,你帮助了社区

754 声望

37 粉丝

0 条评论

得票时间

avatar

bluesboneW

今天,你帮助了社区

754 声望

37 粉丝

宣传栏

1 前置知识

1.1 对this的一个大误解

很多人对this有一个潜意识里的误解——认为this的值取决于其所在函数是在哪里声明的

let obj = {

a: function () {

console.log(this);

},

b: function () {

let f = obj.a;

f();

}

}

obj.b(); // window

很多人在遇到上面这个面试题时,看到函数是在对象内部声明,都会误认为this指向obj

1.2 函数名即指针

this作为一个函数内部的对象,自然与函数紧密相连

然而,很多朋友都没搞清楚函数名竟然是个指针

看下面的代码,思考问题:

  1. fn1是否能够直接找到其函数体的位置(ans:能)
  2. fn1和obj还有关系吗(ans:无关系)

function fn() {

console.log(this);

}

let obj = {

a() {

return fn;

}

}

let fn1 = obj.a();

fn1(); // window

如果上面两个问题你能想明白,或许你对this能够指向window已经有一定的感觉

2 this机制详解

2.1 this本质上就是指向它的调用者

javascript作为一门解释型语言,this的值到底是什么,必须到函数被调用时才能确定

什么意思呢,比如下面这段代码

function fn() {

console.log(this);

}

请问:你现在知道fn里的this是什么吗?

不可能知道的,因为fn没有被调用!

那么什么又叫做必须到函数被调用时才能确定呢?

我们来看看对上面的fn进行不同的调用,结果是什么

function fn() {

console.log(this);

}

let obj = {fn};

fn(); // window

obj.fn(); // obj

可以很明显地发现,因为调用方式的不同,this的值也不同

先别急,整个文章都是围绕这句话展开的

首先,我们分析一下一个函数到底有几种调用方式,再分别进行阐述:

  1. 全局调用
  2. 方法调用
  3. new调用

2.1 全局调用(独立调用)

function foo() {

console.log(this);

}

foo(); // window

对于foo()而言,foo是函数名,而在js中,函数名只是一个指针
类似这种函数名()形式,孤零零地单独出现,《你不知道的javascript》作者把这种方式称之为独立调用,而这种调用会使得被调用函数内部的this默认绑定为window

结合this本质上就是指向它的调用者这句话,全局调用的本质其实就是window调用了foo这个函数

foo();

// 等价于下面

window.foo();

2.2 方法调用

let obj = {

fn() {

console.log(this);

}

}

obj.fn(); // obj

2.2.1 方法调用的就近原则

let obj = {

a: {

b: {

fn() {

console.log(this);

}

}

}

}

obj.a.b.fn(); // obj.a.b

2.2.2 和全局调用进行对比

下面这段代码,在不运行的情况下,很多人都会猜错

let obj = {

a() {

console.log(this);

}

}

let fn = obj.a;

obj.a(); // obj

fn(); // window

相信大家对obj.a();没有疑问,关键在于fn()为什么是window

其实结合1.2小节 函数名即指针一起来看,fn只是一个指针,现在fn指向了obj.a这个函数,而fn()是一个全局调用,因此this自然指向了window

2.3 new

function fn() {

console.log(this);

}

new fn(); // 结果看下面的截图

【JS】this揭秘

3 其它场景下的this解惑

3.1 严格模式下的this

全局作用域里函数中的this是undefined

function test() {

"use strict"

console.log(this)

}

test() // undefined

所以,在使用构造函数时,如果忘了加new,this不再指向全局对象,而是报错,因为这就是函数的全局调用

let People = function (name) {

"use strict"

this.name = name

}

People() // Cannot set property 'name' of undefined

3.2 数组中的this

function fn() {

console.log(this)

}

arr[fn, fn2, fn3]

arr[0]() // ??

// answer:arr

// 解析

// 数组也是对象的一种

// arr[0]() 可以看做 arr.0().call(arr)

3.3 嵌套函数里的this

// 例子一

function fn0() {

function fn() {

console.log(this);

}

fn();

}

fn0(); // fn中this是全局变量

// 例子二

let a = {

b: function () {

console.log(this) // {b:fn}

function xx() {

console.log(this) // window

}

xx()

}

}

a.b()

3.4 setTimeout、setInterval中的this

document.addEventListener('click', function (e) {

console.log(this);

setTimeout(function () {

console.log(this); // 这里的this是全局变量

}, 200);

}, false);

3.5 事件中的this

document.querySelector('div').addEventListener('click',function (e) {

console.log(this) // <div></div>

})

4 自己指定this

4.1 call/apply和bind概览

  1. 我们要将call/apply归为一类,bind单独归为一类

  2. 三者的共同点是都可以指定this
  3. call/apply和bind都是绑定在Function的原型上的,所以Function的实例都可以调用这三个方法

Function.prototype.call(this,arg1,arg2)

Function.prototype.apply(this,[arg1,arg2])

Function.prototype.bind(this,arg1,arg2)

4.2 call/apply —— 第一个参数是this

4.2.1 call/apply的作用

  1. 调用函数

  2. 改变该函数的this指向
  3. 给函数传递参数

返回值
返回值是你调用的函数的返回值

window.a = 1

function print(b, c) {

console.log(this.a, b, c)

}

// 独立调用

print(2, 3) // 1 2 3

// 使用call和apply

print.call({a: -1}, -2, -3) // -1 -2 -3

print.apply({a: 0}, [-2, -3]) // 0 -2 -3

4.2.2 apply传递数组参数

let fn = function () {

console.log(arguments)

}

fn.apply(null, [1, 2, [3, 4]]);

【JS】this揭秘

例子一

Math.max()不接收数组的传递,因此如果想要找到一个长度很长的数组的最大值会非常麻烦

我们可以使用apply方法将数组传递给Math.max()

其本质还是将参数数组拆开再传递给Math.max()

let answer = Math.max.apply(null, [2, 4, 3])

console.log(answer) // 4

// 注意下面三个等价

Math.max.apply(null, [2, 4, 3])

Math.max.call(null, 2, 4, 3)

Math.max(2, 4, 3)

例子二:合并两个数组

非常值得注意的就是arr2数组被拆开了,成了一个一个的参数

// 将第二个数组融合进第一个数组

// 相当于 arr1.push('celery', 'beetroot');

let arr1 = ['parsnip', 'potato']

let arr2 = ['celery', 'beetroot']

arr1.push.apply(arr1, arr2)

// 注意!!!this的意思是要指定调用了push这个方法

// 所以当 this = arr1 后

// 就成了 arr1 调用了 push方法

// 上述表达式等价于 arr1.push('celery', 'beetroot')

console.log(arr1)

// ['parsnip', 'potato', 'celery', 'beetroot']

Math.max.apply(null, [2, 4, 3]) // 完美运行

arr1.push.apply(null, arr2) // 报错 Uncaught TypeError: Array.prototype.push called on null or undefined

// 说明

Math = {

max: function (values) {

// 没用到this值

}

}

Array.prototype.push = function (items) {

// this -> 调用push方法的数组本身

// this为null的话,就是向null里push,会报错

// Array.prototype.push called on null or undefined

}

// 下面三个值是完全等价的,因为this值已经是arr1

Array.prototype.push.apply(arr1, arr2)

arr1.push.apply(arr1, arr2)

arr2.push.apply(arr1, arr2)

4.2.3 小测试

function xx() {

console.log(this)

}

xx.call('1') // ??

xx() // ??

4.3 bind

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

4.3.1 作用

  1. 改变该函数的this指向
  2. 返回一个新函数

4.3.2 绑定函数与目标函数

函数使用bind()方法后会返回一个新函数【绑定函数】
而原函数为【目标函数】

那么新函数被调用时会发生什么呢?
下面一句话务必记住
其实就是把原函数call/apply一下,并指定你传递的this

function xx() {

console.log(this)

}

let foo = xx.bind({'name':'jason'})

// foo —— 新函数【绑定函数】

// xx —— 原函数【目标函数】

foo()

// 新函数调用时对原函数的操作如下(伪代码)

function foo(){

xx.call({'name':'jason'})

}

也就是说,实际上,在foo()这一句执行时做了如下事情:

1.给xx(原函数)指定this

2.调用xx(原函数)
一定要注意这两步是在新函数被调用时才发生,不调用不发生
你也可以总结为一句话:给原函数 call/apply 了一下

4.3.3 bind()传参

  1. bind(this,arg1,arg2...)会将arg1,arg2...插入到新函数【绑定函数】的arguments的开始位置

  2. 调用新函数时,再传递的参数也只会跟在arg1,arg2...后面

function list() {

// 原函数【目标函数】

return Array.prototype.slice.call(arguments);

}

// 新函数【绑定函数】

let leadingThirtysevenList = list.bind(null, 37, 38);

let newList1 = leadingThirtysevenList();

let newList2 = leadingThirtysevenList(1, 2, 3);

let newList3 = leadingThirtysevenList(-1, -2);

console.log(newList1) // [ 37, 38 ]

console.log(newList2) // [ 37, 38, 1, 2, 3 ]

console.log(newList3) // [ 37, 38, -1, -2 ]

4.3.4 使用 this + call/apply原生实现一个bind【重点】

  1. bind的第一个参数是this

  2. bind可以return一个新函数,这个新函数可以调用原函数并且可以指定其this,还可以接受参数
  3. 新函数传递的参数要在bind传递的参数的后面

Function.prototype._bind = function () {

// bind指定的this

let bindThis = arguments[0]

// bind传递的参数

let bindArgs = Array.prototype.slice.call(arguments, 1)

// this指向调用_bind的原函数,即旧函数

let oldFunction = this

// 返回的新函数

return function () {

// 所谓的新函数和旧函数,函数体一样,实际上只是用apply调用了旧函数,再return旧函数的返回值

// 截获调用新函数时传递的参数

let newArgs = Array.prototype.slice.call(arguments)

// 合并bindArgs和newArgs,并且newArgs要在bindArgs后面,再传递给旧函数

return oldFunction.apply(bindThis, bindArgs.concat(newArgs))

}

}

// 测试

function fn() {

console.log(arguments)

}

let newFn = fn._bind(null, 1, 2);

newFn(4, 6)

以上是 【JS】this揭秘 的全部内容, 来源链接: utcz.com/a/109887.html

回到顶部