this
知识线
什么是 this(对象) => 不同情况下的 this 指向(4 种) => 如何改变 this 指针(3 种) => 三者的共同点
与不同点(传参、执行) => call、apply、bind 源码实现
1.什么是 this 指针?
答: this 就是一个对象。不同情况下 this 指向的不同,有以下几种情况。
2.各种情况下的 this 指向问题:
答:
- 对象调用, this 指向该对象(前边谁调用 this 就指向谁)。
var obj = {
name:'小鹿',
age: '21',
print: function(){
console.log(this)
console.log(this.name + ':' + this.age)
}
}
// 通过对象的方式调用函数
obj.print(); // this 指向 obj
- 直接调用的函数, this 指向的是全局 window 对象。
function print(){
console.log(this);
}
// 全局调用函数
print(); // this 指向 window
- 通过 new 的方式, this 永远指向新创建的对象。
function Person(name, age){
this.name = name;
this.age = age;
console.log(this);
}
var xiaolu = new Person('Deer',22); // this = > xaiolu
- 箭头函数中的 this
const obj = {
a:()=>{
console.log(this);
}
}
// 对象调用箭头函数
obj.a(); // window
3.如何改变this指向,以及不同方式的相同点和不同点:
答:通过调用函数的:call、apply、bind三种方式改变this指向
var obj = {
name:'Deer',
age:'2',
adress:'宇宙银河系地球'
}
function print(){
console.log(this); // 打印 this 的指向
}
// 通过 call 改变 this 指向,打印出来是obj的json
print.call(obj,1,2,3);
// 通过 apply 改变 this 指向,打印出来是obj的json
print.apply(obj,[1,2,3]);
// 通过 bind 改变 this 的指向,打印出来是obj的json
let fn = print.bind(obj,1,2,3);
fn();
- 共同点:
功能角度:三者都能改变 this 指向,且第一个传递的参数都是 this 指向的对象。
传参角度:三者都采用的后续传参的形式。 - 不同点:
传参方面: call 的传参是单个传递的(试了下数组,也是可以的),而 apply 后续传递的参
数是数组形式(传单个值会报错),而 bind 没有规定,传递值和数组都可以。
执行方面: call 和 apply 函数的执行是直接执行的,而 bind 函数会返回一个函数,然后我
们想要调用的时候才会执行。
如果我们使用上边的方法改变箭头函数的 this 指针,会发生什么情况呢?能否进行改变
答:由于箭头函数没有自己的 this 指针,通过 call() 或 apply() 方法调用一个函数时,只能传递参
数(不能绑定 this ),他们的第一个参数会被忽略。
4.call、apply、bind 实现(内部实现)
4.1 手写call
- 首先 context 为可选参数,如果不传的话默认上下文为 window ;
- 接下来给 context 创建一个 fn 属性,并将值设置为需要调用的函数;
- 因为 call 可以传入多个参数作为调用函数的参数,所以需要将参数剥离出来;
- 然后调用函数并将对象上的函数删除。
// this 为调用的函数
// context 是参数对象
Function.prototype.myCall = function(context){
// 判断调用者是否为函数
if(typeof this !== 'function'){
throw new TypeError('Error')
}
// 不传参默认为 window
context = context || window
// 新增 fn 属性,将值设置为需要调用的函数
context.fn = this
// 将 arguments 转化为数组将 call 的传参提取出来 [...arguments]
const args = Array.from(arguments).slice(1)
// 传参调用函数
const result = context.fn(...args)
// 删除函数
delete context.fn
// 返回执行结果
return result;
}
4.2 手写apply
- 首先 context 为可选参数,如果不传的话默认上下文为 window
- 接下来给 context 创建一个 fn 属性,并将值设置为需要调用的函数
- 因为 apply 传参是数组传参,所以取得数组,将其剥离为顺序参数进行函数调用
- 然后调用函数并将对象上的函数删除
// 手写一个 apply 方法
Function.prototype.myApply = function(context){
// 判断调用者是否为函数
if(typeof this !== 'function'){
throw new TypeError('Error')
}
// 不传参默认为 window
context = context || window
// 新增 fn 属性,将值设置为需要调用的函数
context.fn = this
// 返回执行结果
let result;
// 判断是否有参数传入
if(arguments[1]){
result = context.fn(...arguments[1])
}else{
result = context.fn()
}
// 删除函数
delete context.fn
// 返回执行结果
return result;
}
// 普通函数
function print(age,age2,age3){
console.log(this.name+" "+ age + " "+ age2+" "+age3);
}
// 自定义对象
var obj = {
name:'小鹿'
}
// 调用函数的 call 方法
print.myApply(obj,[1,2,3])
4.3 手写bind
- 判断调用者是否为函数。
- 截取参数,注意:这里有两种形式传参。
- 返回一个函数,判断外部哪种方式调用了该函数(new | 直接调用)
// 手写一个 bind 函数
Function.prototype.myBind = function (context) {
// 判断调用者是否为函数
if(typeof this !== 'function'){
throw new TypeError('Error')
}
// 截取传递的参数
const args = Array.from(arguments).slice(1)
// _this 指向调用的函数
const _this = this;
// 返回一个函数
return function F(){
// 因为返回了一个函数,我们可以 new F(),所以需要判断
// 对于 new 的情况来说,不会被任何方式改变 this
if(this instanceof F){
return new _this(...args,...arguments)
}else{
return _this.apply(context,args.concat(...arguments))
}
}
}
// 普通函数
function print(){
// new 的方式调用 bind 参数输出换做 [...arguments]
console.log(this.name);
}
// 自定义对象
var obj = {
name:'小鹿'
}
// 调用函数的 call 方法
let F = print.myBind(obj,1,2,3);
// 返回对象
let obj1 = new F();
console.log(obj1);