前言
this是面试中常考的一道题目.对很多面试小白是一道有难度的题目,今天我们来好好聊聊this的问题.
this的指向问题
说到this就不得不说this的指向问题了,this的指向问题遵循以下的规律:
默认绑定:函数被独立调用, this指向window
隐式绑定:函数被某个对象调用, this指向该对象
隐式丢失:函数被一连串的对象调用,this指向最近调用的对象
显示绑定:call apply bind
var name = 'global name';
var foo = function() {
console.log(this.name);
}
var Person = function(name) {
this.name = name;
}
var obj = {
name: 'obj name',
foo: foo
}
var obj1 = {
a:2
fn:foo
}
var obj2 = {
a:3
fm:obj1
}
当调用foo()时,独立调用,触发默认绑定,foo中的this指向全局window,因为全局有var name = 'global name';所以会打印global name.若是obj.foo(),触发隐式绑定,this指向obj,会打印obj name;obj2.fm.fn()会打印2,this指向最近调用的对象
显示绑定就是直接通过call,apply,bind方法改变this指向
let obj={
a:1
}
function foo() {
console.log(this.a);
}
foo.call(obj)//a
call apply bind的区别
在上述代码中foo.call(obj),call帮助foo调用了,如果foo中有参数,call apply bind就应该帮忙接收参数并帮助调用foo.
call函数中接收的第一个参数应该是将this绑定的目标对象,后面的是参数,参数的接收是零散的
例如:
function greet(greeting, punctuation) {
console.log(greeting + ", " + this.name + punctuation);
}
const person = { name: "Alice" };
greet.call(person, "Hello", "!"); // 输出: Hello, Alice!
apply和call的区别就是接收的参数是用数组的包裹的
例如:
function greet(greeting, punctuation) {
console.log(greeting + ", " + this.name + punctuation);
}
const person = { name: "Alice" };
greet.apply(person, ["Hi", "!"]); // 输出: Hi, Alice!
bind接收参数的方式和call一样是零散接收的,但bind返回的是一个函数,所以我们要再调用一下
例如:
function greet(greeting, punctuation) {
console.log(greeting + ", " + this.name + punctuation);
}
const person = { name: "Alice" };
const boundGreet = greet.bind(person, "Hello");
boundGreet("!"); // 输出: Hello, Alice!
手写call apply bind
call:
Function.prototype.mycall = function (context, ...args) {
if (typeof(this) !== 'function') {
throw new TypeError('Error')
}
context = context || window
const key = Symbol('fn')
context[key] = this
const res = context[key](...args)
delete context[key]
return res
}
首先就要判断调用call的对象的数据类型,如果不是一个函数直接抛出错误,第5行是为了防止不传this要指向的对象,如果不传默认指向window;call的实现原理是借用了this的隐式绑定规则.所以在要指向的对象上挂一个fn属性并调用它,为了防止使用时遇上正好有一个fn,所以使用了symbol('fn')做key.再将fn调用后,this就指向了目标对象上了,最后再删除fn.如果this函数中有返回值帮它返回,所以return res
apply:
Function.prototype.myapply = function (context, args) {
if (typeof(this) !== 'function') {
throw new TypeError('Error')
}
context = context || window
const key = Symbol('fn')
context[key] = this
const res = context[key](...args)
delete context[key]
return res
}
bind:
Function.prototype.mybind = function (context, ...args) {
if (typeof(this) !== 'function') {
throw new TypeError('Error')
}
context = context || window
const self = this
return function Fn(...arg) {
if (this.__proto__ === Fn.prototype) { // 被 new 调用 返回 foo 的实例
return new self(...args, ...arg)
} else {
return self.apply(context, [...args,...arg])
}
}
}
bind要注意const boundGreet=foo.bind(obj,1,2)和boundGreet("!")都可以接收参数,优先接收前者情况;第8行是为了防止return出的函数体被new了,导致无法将this指向目标对象,指向window;如果被new调用,第八行的this就指向new出来的实例对象,就会有this.proto === Fn.prototype