文章目录
- 全局上下文中的THIS是WINDOW;
- 块级上下文中没有自己的THIS,它的THIS是继承所在上下文中的THIS的;
- 在函数的私有上下文中,THIS的情况会多种多样
- THIS不是执行上下文(EC才是执行上下文),THIS是执行主体
声明函数的四种方式
let f1 = new Function("x", "y", "return x + y");
function f2 (){};
let f3 = function() {};
let f4 = (x, y) => x + y;
- 前三种支持
this/arguments/new
- 最后一种f4是es6新语法,不支持
this/arguments/new
- f4箭头函数中的
this
就是该函数声明所在的环境(上下文)
如何区分执行主体
- 事件绑定:给元素的某个事件行为绑定方法,当事件行为触发,方法执行,方法中的THIS是当前元素本身(特殊:IE6~8中基于attachEvent方法实现的DOM2事件绑定,事件触发,方法中的THIS是WINDOW而不是元素本身)
- 普通方法执行(包含自执行函数执行、普通函数执行、对象成员访问调取方法执行等):只需要看函数执行的时候,方法名前面是否有“点”,有“点”,“点”前面是谁THIS就是谁,没有“点”THIS就是WINDOW[非严格模式]/UNDEFINED[严格模式]
- 构造函数执行
new Func
:构造函数体中的THIS是当前类的实例 - ES6中提供了箭头函数: 箭头函数没有自己的THIS,它的THIS是继承所在上下文中的THIS
- 可以基于CALL/APPLY/BIND等方式,强制手动改变函数中的THIS指向:这三种模式是和直接很暴力的(前三种情况在使用这三个方法的情况后,都以手动改变的为主)
事件绑定
// 事件绑定 DOM0 DOM2
let body = document.body;
body.onclick = function () {
// 事件触发,方法执行,方法中的THIS是BODY
console.log(this);
};
body.addEventListener('click', function () {
console.log(this); //=>BODY
});
// IE6~8中的DOM2事件绑定
box.attachEvent('onclick', function () {
console.log(this); //=>WINDOW
});
普通方法执行
前面没有执行主体,this就默认指向window
(function () {
console.log(this); //=>window
})();
let obj = {
fn: (function () {
console.log(this); //=>window
return function () { }
})() //把自执行函数执行的返回值赋值给OBJ.FN
};
let obj = {
fn: (function () {
// 自执行函数 this => window
return function () {
console.log(this);
}
})()
};
obj.fn(); // obj
let fn = obj.fn;
fn(); // window
原型链中的this
[].slice(); //=>数组实例基于原型链机制,找到ARRAY原型上的SLICE方法([].slice),然后再把SLICE方法执行,此时SLICE方法中的THIS是当前的空数组
Array.prototype.slice(); //=>SLICE方法执行中的THIS:Array.prototype
[].__proto__.slice(); //=>SLICE方法执行中的THIS:[].__proto__===Array.prototype
构造函数执行
构造函数体中的THIS在“构造函数执行”的模式下,是当前类的一个实例,并且THIS.XXX=XXX是给当前实例设置的私有属性
function Func() {
this.name = "F";
console.log(this);
}
Func.prototype.getNum = function getNum() {
// 而原型上的方法中的THIS不一定都是实例,主要看执行的时候,“点”前面的内容
console.log(this);
};
let f = new Func;
f.getNum();
f.__proto__.getNum();
Func.prototype.getNum();
箭头函数执行
不建议乱用箭头函数(部分需求用箭头函数还是很方法便的)
let obj = {
func: function () {
console.log(this);
},
sum: () => {
console.log(this);
}
};
obj.func(); //=>THIS:OBJ
obj.sum(); //=>THIS是所在上下文(EC(G))中的THIS:WINDOW
obj.sum.call(obj); //=>箭头函数是没有THIS,所以哪怕强制改也没用 THIS:WINDOW
定时器中的匿名回调函数
回调函数中的THIS一般都是WINDOW(但是有特殊情况)
解决方法
1、用于保存this的变量_this
let obj = {
i: 0,
// func:function(){}
func() {
// THIS:OBJ
let _this = this;
setTimeout(function () {
// THIS:WINDOW 回调函数中的THIS一般都是WINDOW(但是有特殊情况)
_this.i++;
console.log(_this);
}, 1000);
}
};
obj.func();
2、基于bind把函数中的this预先处理
let obj = {
i: 0,
func() {
setTimeout(function () {
// 基于BIND把函数中的THIS预先处理为OBJ
this.i++;
console.log(this);
}.bind(this), 1000);
}
};
obj.func();
3、箭头函数中没有自己的THIS,用的THIS是上下文中的THIS,也就是obj
let obj = {
i: 0,
func() {
setTimeout(() => {
this.i++;
console.log(this);
}, 1000);
}
};
obj.func();
隐式的this
js会自动给你传入this
fn(1, 2) === fn.call(undefined, 1, 2);
obj.method("hi") === obj.method.call(obj, "hi");
arr[0](1, 2); === arr[0].call(arr, 1, 2)
例如
let fn = function(x, y){console.log(this)};
fn(1, 2)
let obj = {
fn
}
obj.fn(1, 2) //
let arr = [fn, 2];
arr[0](1,2);
手动改变this
call
call 原理
let res = fn.call(obj, 10, 20);
console.log(res);
- fn首先基于__proto__找到Function.prototype.call,并且让call方法执行
- 在call方法执行的过程中(call方法中的this->fn),把fn执行,并且让fn中的this变为传递的第一个参数obj,
- 再并且把10/20当做实参传递给fn,最后接收fn执行的返回值,把返回值作为call方法的返回值返回
call应用:把类数组转换为数组
- arguments虽然是类数组,但是结构和数组一样(除了__proto__不是Array.prototype)
- 所以操作数组的代码和操作arguments基本一致的(尤其是循环这种东西)
- 如果我能让ARRAY原型上的slice执行,让方法中的this变为arguments,相当于把arguments转换为一个数组
- 只要两个实例结构类似,那么大部分操作操作的他们方法都可以公用,无外乎就是THIS指向的问题
类数组操作
var arr = [];
for (var i = 0; i < arguments.length; i++) {
arr.push(arguments[i]);
}
return arr;
让slice方法中的this变为arguments
[].slice.call(arguments)
Array.prototype.slice.call(arguments)
let utils = (function () {
function toArray() {
return [].slice.call(arguments);
}
return {
toArray
};
})();
let ary = utils.toArray(10, 20, 30); //=>[10,20,30]
console.log(ary);
ary = utils.toArray('A', 10, 20, 30); //=>['A',10,20,30]
console.log(ary);
使用forEach
[].forEach.call(arguments, item => {})
手写call
如何让fn中的this变为obj => obj.fn() => 需要保证fn函数作为obj某个成员的属性值
思路:把函数作为要改变的THIS对象的一个成员,然后基于对象的成员访问执行函数即可obj.fn=fn; obj.fn();
原理如图:对象.属性
成员访问时,this指向对象本身
Function.prototype.call = function call(context, ...params) {
context = context == null ? window : context;
let result;
// 把函数作为对象的某个成员值
context["fn"] = this;
// 基于 对象[成员]() 方式把函数执行,此时函数中的this就是对象,(把参数传递给函数,并且接收返回值)
result = context["fn"](...params);
// 设置的成员用完后删掉
delete context["fn"];
// 把函数的返回值作为call方法执行的结果返回
return result;
}
- 如果原始对象里有名为fn的属性,就会覆盖掉
解决办法:通过Symbol创建一个唯一值 - context必须是个对象,否则无法设定属性值
-
如果不是对象,可以利用其构造函数的
constructor
转为对应类型,但是注意此方法不适用symbol和bigint类型,他们不允许被new
-
也可以通过
Object()
转为对象
-
优化
Function.prototype.call = function call(context, ...params) {
context = context == null ? window : context;
// 必须要保证CONTEXT得是一个对象
let contextType = typeof context;
if (!/^(object|function)$/i.test(contextType)) {
// context.constructor:当前值所属的类
// context = new context.constructor(context); //=>不适合Symbol/BigInt
context = Object(context);
}
let result;
let key = Symbol('KEY');
// 把函数作为对象的某个成员值(成员名唯一:防止修改原始对象的结构值)
context[key] = this;
// 基于“对象[成员]()”方式把函数执行,此时函数中的THIS就是对象(把参数传递给函数,并且接收返回值)
result = context[key](...params);
// 设置的成员用完后删除掉
delete context[key];
// 把函数的返回值作为CALL方法执行的结果返回
return result;
};
call 的深层理解
var name = 'lc';
function A(x, y) {
var res = x + y;
console.log(res, this.name);
}
function B(x, y) {
var res = x - y;
console.log(res, this.name);
}
B.call(A, 40, 30); // 10 "A"
B.call.call.call(A, 20, 10); // => A.call(20, 10) => NaN undefined
Function.prototype.call(A, 60, 50); // 无任何输出
Function.prototype.call.call.call(A, 80, 70); // NaN undefined
根据call源码拆分每一步
B.call(A,40,30); // 10 "A"
B.call.call.call(A, 20, 10); // NaN undefined
Function.prototype.call(A, 60, 50);
Function.prototype.call.call.call(A, 80, 70);
同第二步
bind
想要改变点击事件的this
function func(x, y, ev) {
console.log(this, x, y, ev);
}
const obj = {
name: "test"
};
document.body.onclick = func; //=>this:body x:MouseEvent y:undefined
document.body.onclick = func.call(obj, 10, 20);
这样处理不行,事件绑定,绑定的是一个方法,此处是先把func执行(做了一些处理),把方法执行的返回结果赋值给事件绑定
解决方法:包一层匿名函数先执行call
document.body.onclick = function anonymous(ev) {
func.call(obj, 10, 20, ev);
};
和直接用bind是一样的效果
document.body.onclick = func.bind(obj, 10, 20);
手写bind
- 利用科里化函数的编程思想,预先把需要处理的函数/改变的this以及传递的实参等信息存在闭包中,后期满足条件(事件触发/定时器等),先执行返回的匿名函数,在执行匿名函数的过程中再去改变this等
function func(x, y, ev) {
console.log(this, x, y, ev);
}
const obj = {
name: "test"
};
Function.prototype.bind = function bind(context, ...params) {
// this -> 处理的函数 func
// context -> 要改变的函数中的THIS指向 obj
// params -> 最后给函数传递的实参 [10,20]
// 保存func,便于匿名函数中执行,也可以使用箭头函数,就不需要保存this了
let _this = this;
// 返回一个匿名函数
return function /* anonymous */(...args) {
// args -> 可能传递的事件对象等信息 [MouseEvent]
// this -> 匿名函数中的THIS是由当初绑定的位置触发决定的(总之不是func要处理的函数),所以需要保存_this
_this.call(context, ...params.concat(args))
}
}
document.body.onclick = func.bind(obj, 10, 20)
apply
apply应用:获取数组中的最大值
使用排序
console.log(arr.sort((a, b) => b - a)[0])
利用Math.max
Math.max 需要一项项传入数值进行比较
let arr = [4, 6, 10, 3, 5];
console.log(Math.max.apply(Math, arr))
例题
1
var num = 10;
var obj = {
num: 20
};
obj.fn = (function (num) {
this.num = num * 3;
num++;
return function (n) {
this.num += n;
num++;
console.log(num);
}
})(obj.num);
var fn = obj.fn;
fn(5);
obj.fn(10);
console.log(num, obj.num);
2
以下代码的this是什么
button.onclick = function(e) {
console.log(this)
}
回答:不知道
因为this是call
的第一个参数,这里并未执行这个事件,所以未传入this
标准回答:
-
这个this是不确定的,需要看它如何调用
-
如果点击的是button的时候,浏览器会将button作为this传进来
-
如果通过其它方式调用就要看传入的this是什么了
let button = document.createElement('button'); button.innerHTML = "button"; document.querySelector('body').appendChild(button); button.onclick = function(e) { // 点击指向button console.log(this) } let fn = button.onclick; fn(); // 不传this自调用默认指向window fn.call({name: "LC"}) // 自调用传入指定this
3
注意点
- 用
let
声明的变量不会挂在window
上 - 判断this时可以用
call
来尝试改写调用函数
let length = 10
function fn(){console.log(this.length)}
let obj = {
length: 5,
method(fn){
fn()
arguments[0]()
}
}
obj.method(fn, 1)
调用的第一个fn()
的this就是指向window
,window.length
let obj = {
length: 5,
method(fn){
fn.call(window)
}
}
第二个调用中this指向的是arguments
,输出arguments
的长度
arguments[0].call(arguments)
let length = 10
function fn(){console.log(this.length)}
let obj = {
length: 5,
method(fn){
arguments[0].call(arguments) // 4
}
}
obj.method(fn, 1, 2, 3)