题外篇git
如何改变this
得指向,常见的四种操做以下es6
call、apply、bind
let that = this
es6
中使用箭头函数new
操做关于this
的指向github
在一个函数上下文中,this
由调用者提供,由调用函数的方式来决定。若是调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this
指向该对象。若是函数独立调用,那么该函数内部的this
,则指向undefined
。可是在非严格模式中,当this
指向undefined
时,它会被自动指向全局对象。数组
参考来自 波波老师bash
call()
方法调用一个函数, 其具备一个指定的this值和分别地提供的参数(参数的列表)。闭包
语法:app
fun.call(thisArg, arg1, arg2, ...)
复制代码
thisArg:fun
函数运行时指定得this
值函数
this
得值可能有以下几种可能:测试
null,undefined
or 不传,this
默认指向window
对象fn,this
指向fn
函数this
指向这个对象看个例子ui
let obj = {
val:'call'
}
function fn () {
console.log(this.val,'testCall');
}
fn.call(obj) //'call','testCall'
复制代码
先考虑能够正常执行,后面的传参暂时不考虑
琢磨一下上面例子的代码执行过程
call()
在执行过程当中,咱们想象一下它大概会经历一些几个阶段(真实原理不作介绍)
fn
方法复制到obj
对象中fn
函数的this
指向fn
函数执行fn
从obj
对象删除分析: 那么咱们在模拟代码的场景下 fn.call(obj)
的执行过程能够想象成以下步骤:
一、将fn复制到obj对象中,那么以下也就修改了fn中this的指向
obj = {
val:'call',
fn:function(){
console.log(this.val,'testCall')
}
二、 执行fn()
obj.fn()
三、删除fn这个key
delete obj.fn
复制代码
Function.prototype.call2 = function(args){
//此时的args就是 上面的obj
//1,此时使用this来获取调用call的方法
args.fn = this;
//第二步 调用执行fn()
args.fn();
//第三步 删除方法
delete args.fn
}
//测试下
let obj = {
val:'call2'
}
function fn () {
console.log(this.val,'testCall2');
}
fn.call2(obj) //'call2','testCall2'
复制代码
MDN
文档上介绍过,call
能够接受多个参数,那么在第二版的时候咱们加上入参这个功能
栗子
let obj = {
val:'call'
}
function fn (name) {
console.log(this.val,name);
}
fn.call(obj,'alan') //'call','alan'
复制代码
分析:
Arguments
中获取第二个开始到最后结束的参数就好了// 第二版
Function.prototype.call2 = function(...args) {
//利用es6的 rest 来获取函数的传参,以及传入thisArg;
let [thisArg,...arr] = args ;
// 获取调用的函数方法
thisArg.fn = this;
// 用解构执行函数
thisArg.fn(...arr)
//删除
delete thisArg.fn
}
let obj = {
val:'call'
}
function fn (name) {
console.log(this.val,name);
}
fn.call2(obj,'alan') //'call','alan'
复制代码
解释:
es6
的 rest (形式为...变量名),这样能够获得一个数组,即args
此时为数组,那么上文中的thisArg
就是传递的第一个参数。fn(..arr)
使用了es6
spread ,他就比如是reset
的逆运算,这样操做之后无论传递了几个参数均可以正常处理文章开头介绍过,若是在严格模式下,传
null,undefined
or 不传,thisArg
默认指向window
对象,还有一种场景若是fn
方法有返回值的状况。
栗子 1
var val = 'call'
function fn () {
console.log(this.val);
}
fn.call() //'call'
fn.call(null);//'call'
fn.call(undefind);//'call'
复制代码
分析: 若是不传值或传null
等值,处理起来不算麻烦,稍微在咱们原来的版本上作一些修改就好,看以下代码
// 3.1
Function.prototype.call2 = function(...args) {
let thisArg,arr = [];
if(args.length === 0 || !args[0]){
thisArg = window;
} else{
//利用es6的解构来获取函数的传参,以及传入thisArg;
[thisArg,...arr] = args ;
}
// 获取调用的函数方法
thisArg.fn = this;
// 用解构执行函数
thisArg.fn(...arr)
//删除
delete thisArg.fn
}
fn.call2() //'call'
fn.call2(null);//'call'
fn.call2(undefind);//'call'
复制代码
栗子2
let obj = {
val:'call'
}
function fn (name) {
console.log(this.val,name);
return {
val:this.val,
name:name
}
}
fn.call(obj,'alan') //'call','alan'
//
{
val:'call',
name:'alan'
}
复制代码
终极版本
// 3.2
Function.prototype.call2 = function(...args) {
let thisArg,arr = [];
if(args.length === 0 || !args[0]){
thisArg = window;
} else{
//利用es6的解构来获取函数的传参,以及传入thisArg;
[thisArg,...arr] = args ;
}
// 获取调用的函数方法
thisArg.fn = this;
// 用解构执行函数
let result = thisArg.fn(...arr)
//删除
delete thisArg.fn
return result
}
let obj = {
val:'call'
}
function fn (name) {
console.log(this.val,name);
return {
val:this.val,
name:name
}
}
fn.call2(obj,'alan') //'call','alan'
//
{
val:'call',
name:'alan'
}
复制代码
apply的实现方式跟call基本类似,就是在传参上,apply接受的是数组,直接就贴一下代码
Function.prototype.apply2 = function(thisArg,arr) {
if(!thisArg){
thisArg = window;
}
// 获取调用的函数方法
thisArg.fn = this;
// 用解构执行函数
let result = thisArg.fn(...arr)
//删除
delete thisArg.fn
return result
}
let obj = {
val:'apply'
}
function fn (name) {
console.log(this.val,name);
return {
val:this.val,
name:name
}
}
fn.apply2(obj,['alan']) //'apply','alan'
复制代码
bind()
方法建立一个新的函数,在调用时设置this
关键字为提供的值。将给定参数列表做为原函数的参数序列的前若干项。
语法:
fun.bind(thisArg,arg1,arg2......)
复制代码
bind()
方法会建立一个新的函数,通常叫绑定函数
能够接受参数,这个地方注意,它能够在bind
的时候接受参数,同时bind()
返回的新函数也能够接受参数
栗子
var obj = {
val: 'bind'
};
function fn() {
console.log(this.val);
}
// 返回了一个函数
var bindObj = fn.bind(obj);
bindObj(); // bind
复制代码
照旧,暂时不考虑传参
分析:
bindObj()
的执行结果跟使用call
同样的,不一样的是它须要调用返回的方法bindObj
琢磨上述代码执行过程,这个时候咱们对比一下call
的模拟来看
bindObj
像是call
模拟过程当中的fn
,然后bindObj()
就像是fn()
bind
返回的函数,咱们能够想象成call()
调用只有返回的函数而不会执行,只是apply(),call()
是当即执行,而bind
须要再次调用执行Function.prototype.bind2 = function (args) {
//经过this拿到调用方法
let that = this;
//使用一个闭包来存储call方法的结果
return function () {
return that.call(args);
}
}
var obj = {
val: 'bind'
};
function fn() {
console.log(this.val);
}
// 返回了一个函数
var bindObj = fn.bind2(obj);
bindObj(); // bind
复制代码
考虑下传参的场景,开头介绍过,传参有两种场景
栗子
let obj = {
val:'bind'
};
function fn(name,sex){
let o = {
val:this.val,
name:name,
sex:sex
}
console.log(o)
}
let bindObj = fn.bind(obj,'alan');
bindObj('man'); //{ val: 'bind', name: 'alan', sex: 'man' }
复制代码
栗子分析:
bind
的时候接受了一个参数name
,同时返回了一个函数sex
模拟分析:
bind
方法传参的场景,咱们能够借用以前在call
函数中的方法,使用es6 rest
的方法。获取从第二个开始到结束的全部参数bind
返回的函数传参,能够在写的时候,将bind
传参跟后续的传参合并模拟开发
// 2.1
Function.prototype.bind2 = function (args) {
//经过this拿到调用方法
let that = this;
// 获取bind2函数从第二个参数到最后一个参数
let allArgs = Array.prototype.slice.call(arguments, 1);
return function () {
// 这个时候的arguments是指bind返回的函数传入的参数
var bindArgs = Array.prototype.slice.call(arguments);
return that.apply(args, allArgs.concat(bindArgs));
}
}
//2.2 es6实现
Function.prototype.bind2 = function (...args) {
//利用es6的 rest 来获取函数的传参,以及传入thisArg;allArgs就是第二个参数到最后一个参数的数组
let [thisArg,...allArgs] = args ;
let that = this;
return function (...bindArgs) {
return that.apply(thisArg, allArgs.concat(bindArgs));
}
}
let obj = {
val:'bind'
};
function fn(name,sex){
let o = {
val:this.val,
name:name,
sex:sex
}
console.log(o)
}
let bindObj = fn.bind2(obj,'alan');
bindObj('man'); //{ val: 'bind', name: 'alan', sex: 'man' }
复制代码
说明
Array.prototype.slice.call(arguments)
是如何将arguments
转换成数组的,首先调用call
以后,this
就指向了arguments
,或许咱们能够假象一下slice
的内部实现是:建立一个新的数组,而后循环遍历this
,将this
的没一个值赋值给新的数组而后返回新数组。大佬若是看到文中若有错误的地方欢迎指出,我及时修正。