JavaScript 中函数的调用方式决定了 this 的值(运行时绑定)
各种情形下的 this 指向
1,在构造函数中,this 就是实例对象
let _this = null
function Parent(name, age) {
this.name = name
this.age = age
_this = this
}
const p = new Parent('zhangsan', 18)
console.log(p === _this) // true;
2,以方法调用的函数,this 就是调用的对象
let _this = null
const obj = {
name: 'zhangsan',
sayName() {
console.log(this.name)
_this = this
}
}
obj.sayName() // zhangsan
console.log(obj === _this) // true;
3,在 DOM事件中,this 就是触发事件的元素
<body>
<button id="btn">btn</button>
</body>
<script>
const btn = document.getElementById('btn')
btn.addEventListener('click', function() {
console.log(this === btn) // true
})
</script>
4,作为函数直接调用时,this 指向全局对象
<script>
function consoleThis(){
console.log('this', this);
}
consoleThis(); // Window
</script>
function consoleThis(){
console.log('this', this);
}
consoleThis() // global
5,在浏览器环境下,不通过函数,直接使用 this,this 指向全局变量
<script>
console.log(this); // window
</script>
6,在 nodejs 的模块文件中,直接使用 this,this 指向 默认的 module.exports
console.log(this === module.exports);
const modulea = require('./a.js')
运行 node b.js会输出 true
7,在箭头函数中使用 this,this 的取值就是在箭头函数被创建时,当前上下文中的 this,和调用方式无关
下面代码中,外部arrFn 在创建时,它外部的 this 是 module.exports,所以无论通过哪种调用方式, this 都是module.exports
而 Foo 中的 arrFn 在创建时,它外部的 this,是函数 Foo中的 this,所以它打印出的 this,一直和 Foo 中直接打印的 this 是一致的
const arrFn = () => this
const callArr = function() {
return arrFn()
}
const obj = {
name: 'zhangsan',
callArr,
arrFn,
objArr: () => this
}
console.log(arrFn() === module.exports) // true;
console.log(obj.arrFn() === module.exports) // true;
console.log(obj.callArr() === module.exports) // true;
console.log(obj.objArr() === module.exports) // true;
function Foo() {
console.log('Foo this', this);
const arrFn = () => {console.log(this);}
arrFn()
}
Foo() // 打印 2 次 global
new Foo() // 打印 2 次 Foo
obj.Foo = Foo
obj.Foo() // 打印 2 次 obj
const obj2 = {
name: 'obj2'
}
Foo.bind(obj2)() // 打印 2 次 obj2
Foo.call(obj2) // 打印 2 次 obj2
Foo.apply(obj2) // 打印 2 次 obj2
8,通过 call,apply,bind 可以修改 this 的指向,传入的第一个参数就是 this 的指向
const obj = {
name: 'zhangsan'
}
function consoleThis(){
console.log('this', this);
}
consoleThis() // global
consoleThis.call(obj) // obj
consoleThis.apply(obj) // obj
consoleThis.bind(obj)() // obj
实现 call,apply
实现方式:
- 将该函数(也就是原有的 this)绑定为传入的this对象的属性
- 通过传入的 this对象调用该函数
注意点:
- 需要判断调用者必须是个函数
- 需要判断传入的 context 如果为 null 或 undefined,改为全局对象
- 在绑定方法的时候可能会覆盖context原有的方法,所以属性名最好是用 Symbol
- 调用结束后要删除掉绑定的属性,避免污染传入的 context
- call 和 apply要 return 函数调用的返回值
Function.prototype.myCall = function(context, ...args) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
if (context === null || context === undefined) {
context = window || global
} else {
context = Object(context)
}
var fnName = Symbol()
context[fnName] = this
const result = context[fnName](...args)
delete context[fnName]
return result
}
Function.prototype.myApply = function(context, args = []) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
if (context === null || context === undefined) {
context = window || global
} else {
context = Object(context)
}
var fnName = Symbol()
context[fnName] = this
const result = context[fnName](...args)
delete context[fnName]
return result
}
实现 bind
在实现 bind 时,除了上面的一些判断,还需要注意的一些细节
1,bind 在绑定的时候,是可以进行科里化传参的,保留前置传入的参数
function foo(name, age, addr) {
console.log(this.value, name, age, addr);
}
const obj = {
value: 'testbind'
}
const b0 = foo.bind(obj)
b0() // testbind undefined undefined undefined
const b1 = foo.bind(obj, 'zhangsan')
b1() // testbind zhangsan undefined undefined
const b2 = b1.bind(obj, 12)
b2() // testbind zhangsan 12 undefined
b2('beijing') // testbind zhangsan 12 beijing
2,返回的函数,如果作为构造函数使用,提供的 this 会被忽略,但前置的参数仍然会保留
function foo(name, age, addr) {
console.log(this.value, name, age, addr);
}
const obj = {
value: 'testbind'
}
const b1 = foo.bind(obj, 'zhangsan')
b1() // testbind zhangsan undefined undefined
new b1() // undefined zhangsan undefined undefined
3,实现思路
- 对于参数保留,将本次传入的...args和返回值函数调用的arguments组装,最后使用 apply 调用
- 对于返回函数的处理,如果是通过 new 调用,当前的this 就应该是实例对象,也就是F 函数中的 this,否则是传入的 context
- 由于返回的函数时可以 new 创建实例的,所以要修改 F 的原型,指向最初调用时函数的原型,否则原型链就被破坏了。修改后再使用 new 调用返回的函数,原型和直接 new foo 是一致的
4,具体代码和测试
Function.prototype.myBind = function(context, ...args) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
const _this = this
function F() {
const bindArgs = Array.prototype.slice.call(arguments)
return _this.apply(this instanceof F ? this : context, args.concat(bindArgs))
}
F.prototype = Object.create(this.prototype)
return F
}
// 测试代码
function foo(name, age, addr) {
console.log(this.value, name, age, addr);
}
foo.prototype.prototypeBindProperty = 'prototype bind property'
const obj = {
value: 'testbind'
}
const b0 = foo.myBind(obj)
b0() // testbind undefined undefined undefined
const b1 = foo.myBind(obj, 'zhangsan')
b1() // testbind zhangsan undefined undefined
const res = new b1() // undefined zhangsan undefined undefined
console.log(res.prototypeBindProperty); // prototype bind property
const b2 = b1.myBind(obj, 12)
b2() // testbind zhangsan 12 undefined
b2('beijing') // testbind zhangsan 12 beijing