this取值是在函数执行是确定的:
- 1.普通函数 ://window
- 2.普通函数的call方法fn.call({name:1})://{name:1}
- 3.普通函数的bind方法fn.bind({name:1})://{name:1}
const fn2= fn1.bind({name:1})
fn2()//bind会返回一个新的函数执行
- 4.对象的方法执行,this指向该对象 obj.say()
- 5.setTimeout(function(){console.log(this)},2000)function里面的this指向window
- 6.setTimeout(()=>{},2000)指向外面的对象
- 7.class中的指向实例本身
第一部分:常见的this指向问题
1. 全局上下文中的 this
在浏览器环境中,全局上下文中的 this,就是 window。
let a = 12;
console.log(this); // 控制台输出为 window
复制代码
2. 块级上下文中的 this
- 块级上下文中没有自己的 this,它的 this 是继承所在上级上下文的 this。
- this是执行主体,不是执行上下文;执行上下文是EC ; ( 比如我在北京饭店吃饭,我就是this,北京饭店是执行上下文).
let obj = {
fn() {
// fn函数中的this是obj
{
let a = 12;
console.log(this); // 输出obj。继承上级上下文,即函数fn的私有上下文中的this
}
},
};
obj.fn();
复制代码
3. 给事件绑定方法中的this
函数私有上下文中的 this 千变万化,可以总结为以下五种情况:
3.1 事件绑定
给元素的某个事件行为绑定方法,事件触发时方法执行,此时方法中的 this一般为当前元素本身
。
let body = document.body;
//DOM0中事件绑定
body.onclick = function () {
console.log(this); // body
};
复制代码
3.2 在 element 上绑定事件
<div class="container">
<div id="aa" onclick="clicke()"></div>
</div>;
function clicke() {
console.log(this); // Window
}
复制代码
3.3 js 绑定 onclick 事件
this 指向该元素
<div class="container">
<div id="aa" onclick="clicke()"></div>
</div>;
document.getElementById("aa").onclick = function () {
console.log(this); // <div id="aa"></div>
};
复制代码
3.4 js 使用 addEventListener 绑定事件
此时的 this 指向 该元素, 注意: 在 IE 浏览器中,使用为 attachEvent(), this 指向全局变量
<div class="container">
<div id="aa" onclick="clicke()"></div>
</div>;
document.getElementById("aa").addEventListener("click", function () {
console.log(this); // <div id="aa"></div>
});
复制代码
3.5 jquery 的 3 种绑定 click 事件
此时的 this 均指向该元素
$("#aa").bind("click", function () {
console.log(this); // <div id="aa"></div>
});
$("#aa").click(function () {
console.log(this); // <div id="aa"></div>
});
$("#aa").on("click", function () {
console.log(this); // <div id="aa"></div>
});
复制代码
4. 普通函数执行中的this
这里的普通方法包含:普通函数、自执行函数、对象成员访问调取方法执行…等。
只要看函数执行时,方法名前是否有“点”就是了。
如果有“点”——xxx.方法()中 this 就是 xxx;如果没有“点”——this 即为 window(严格模式下为 undefined)。
// 自执行函数
(function(){
console.log(this); // 非严格模式下输出window,严格模式下输出undefined
})();
let obj = {
fn: (function(){
console.log(this); // 输出window
return function(){};
})();
}
/*
上面两个例子都是自执行函数执行,输出均为window。说明this的指向,与函数在哪
定义和执行的无关。自执行函数中的this一般都为window(因为函数执行时前面没有“点”)
*/
复制代码
function func(){
console.log(this);
}
let obj = {
func:func
}
// 普通函数执行
func(); // this:window
// 成员访问方法执行
obj.func(); // this:obj
[].slice()方法中this->当前空数组。
Array.prototype.slice()方法中的this->Array.prototype。
复制代码
涵盖以上两种情况的一道题: 点击body输出什么?
function func(){
console.log(this);//Window
}
document.body.onclick = function(){
console.log(this)//HTMLBodyElement
func();
}
/*
body
window
*/
复制代码
5 构造函数执行的this
构造函数体中的 this 指向的就是当前类的实例。因此,函数中出现的 this.xxx = xxx 这样的代码就是给当前实例设置私有属性;
换句话说,类中凡是不带 this 的代码统统都与它创建的实例无关,充其量也就是这个函数私有上下文中的私有变量而已。
function Func() {
console.log(this);
// 构造函数执行,构造函数体中的this指向当前类的实例——f,因此输出的this为f
}
Func.prototype.getNum = function getNum() {
console.log(this);
};
let f = new Func();
//原型上方法中的this不一定是实例,要看执行时前面有没有点( 即当作普通函数看待 )
f.getNum(); //f
f.__proto__.getNum(); //f.__proto__
Func.prototype.getNum(); //Func.prototype
复制代码
6. ES6 中的箭头函数
普通函数执行过程: | 箭头函数执行: |
---|---|
形成私有上下文和(AO) 初始化作用域链 初始化 this 初始化 arguments 形参赋值 变量提升 代码执行 |
形成私有上下文和(AO) 初始化作用域链 形参赋值 变量提升 代码执行 |
- 箭头函数没有自己的 this,它的 this 继承自上级上下文中的 this===>这一点和块级上下文类似。
let obj = {
func:function(){
console.log(this);
}
sum:()=>{
console.log(this);
}
}
obj.func(); // this:obj 对象成员访问
obj.sum(); // this:window
obj.sum.call(obj); // this:window 箭头函数没有this,改也没用
复制代码
7. 回调函数中的 this
回调函数中的 this 一般指向 window,但也有例外;、
let obj = {
i: 0,
func() {
console.log("func", this); //obj
setTimeout(function () {
console.log("定时器里面的", this); //window
}, 1000);
},
};
obj.func();
复制代码
如何改写 this?
1.赋值that=this
let obj = {
i:0,
let that=this;
func(){
setTimeout(function(){
that.i++;
console.log(that); // {i:1,func:f}
},1000);
}
}
obj.func();
复制代码
2.用bind强行改 function () {}.bind(this)
let obj = {
i: 0,
func() {
setTimeout(
function () {
this.i++;
console.log(obj); // {i:1,func:f}
}.bind(this),
1000
); // 这里强行让内部这个回调函数中的this指向外层的this(即obj)
},
};
obj.func();
复制代码
3.用箭头函数
//第三种解决方案:回调函数用箭头函数,箭头函数中没有自己的this,这里用的this是继承自外层的,也就是func函数的this->obj!
let obj = {
i: 0,
func() {
setTimeout(() => {
this.i++;
console.log(obj); // {i:1,func:f}
}, 1000);
},
};
obj.func();
复制代码
第二部分:改变this指向
- call/apply/bind 方法强制手动改变函数中的 this 指向
- 这三个方法都在函数类的原型对象 Function.prototype 上,因此所有的函数都能基于原型链proto找到并调用这三个方法 ;
2.1 call
[function].call([context],params1,params2,…)
含义:执行 call 方法时,会将后面的参数传递给[function]执行,并把函数中的 this 修改为[context]:
@ [function]: 希望改变自身 this 的那个函数
@ [context]: 希望把 this 变为它!
@ params1, params2, … : 传递给函数的参数
第一个参数不传 / null / undefined时:
非严格模式——this:window
严格模式——传谁this就是谁,不传就是undefined
复制代码
2.2 apply
与 call 方法相同,只不过传递函数实参以数组的方式。
[function].call([context],[params1,params2,…])
复制代码
2.3 bind
bind 的语法与 call 相同,作用与以上两个方法均不同。
它是预先修改 this,预先存储参数,而函数不被立即执行;
也就是说,call / apply 都是把函数立即执行的,并且改变 this。
[function].call([context],params1,params2,…)
复制代码
2.4案例分析
需求:把func函数绑定给body的click事件,触发body的click事件时,func函数执行要让func函数中的this指向obj,并给func函数传递参数10,20;
方案一:不可行
document.body.onclick = func.call(obj, 10, 20);
/*
不可取func.call(obj,10,20)会立即执行func函数(并修改this&传参),
它哪管你什么时候点击body。这种写法实际上func.call(obj,10,20)执行的结果作为值绑定给body点击事件。
*/
复制代码
方案二:可行
document.body.onclick = function anonymous() {
func.call(obj, 10, 20);
};
/*
将一个匿名函数绑定给body的点击事件,也就是将这个匿名函数的地址绑定给了事件,
并没有立即执行。当点击事件触发时,将匿名函数执行,然后执行func.call。
事实上,在bind方法之前,大家就是这样处理的。已经和bind的原理非常类似了。
*/
复制代码
方案三:可行
document.body.onclick = func.bind(obj, 10, 20);
/*
bind函数不会立即执行,而是预先修改this,并预先存储需要传递的参数。后续需要时(事件触发)再执行函数。
*/
复制代码
2.5 bind 原理
function func(x, y) {
console.log(this, x, y); //改了之后就是obj
}
Function.prototype.bind1 = function bind1(context = window, ...params) {
/*
1.bind方法执行的时候,预先修改this,预先存储参数,而函数不被立即执行,所以要
返回一个函数特定时(比如点击之后)候执行
*/
//2.bind执行的时候,因为是func调用的所以this指向func
console.log("bind函数里面的this:", this); //func
//3.当点击事件时会执行bind()的返回结果,因为func.bind()执行后返回一个函数,被onclick绑定了
let that = this;
return function anonymous() {
//匿名函数的this指向window的,要改变func的this,让that=this
console.log("匿名函数的this", this); //window
//改变this,并执行函数
that.call(context, ...params);
};
};
let obj = {};
onclick = func.bind1(obj, 10, 20);
/*
第一步:bind传参设置:如果不传参,this默认为改为window,后面的参数用展开运算符接受
第二步:特定条件下,bind才会改变this指向,比如点击了才改变this指向,而func.bind1(obj, 10, 20);时立即执行的,所以他的执行结果时返回一个函数,让点击事件绑定
第三步:bind执行的时候,因为是func调用的所以this指向func;而返回的供点击事件执行的匿名函数他的this是window;
第四步:bind函数里面让that=this,然后在匿名函数里面执行that.call()也就是func.call()
*/
复制代码
有点乱,简化一下:
function func(x, y) {
console.log(this, x, y); //改了之后就是obj
}
Function.prototype.bind1 = function bind1(context = window, ...params) {
let that = this;
return function anonymous() {
that.call(context, ...params);
};
};
let obj = {};
onclick = func.bind1(obj, 10, 20);
复制代码
利用 apply实现bind方法 改变 this 指向:
Function.prototype.bind = function bind(context = window, ...params) {
//这是bind函数的this指向func,因为调用了func.bind
var that = this;
return function (...inners) {
//this指向body
that.apply(context, params.concat(inners));
};
};
body.onclick = func.bind(obj, 10, 20, 30);
/*
利用闭包机制预先把需要执行的函数以及改变的this以及后续需要给函数传递的参数信息
都保存到不释放的上下文中,后续使用的时候直接拿出来用
*/
复制代码
2.6 call,apply 应用
把类数组转化为数组
类数组为对象,但有一些数组的方法,用起来像数组,有索引,length,和可迭代性;
常见的有:函数实参集合,dom 元素集合,dom 节点集合
把类数组转化为数组就可以用数组原型上的方法
1.Array.from()
function toArray() {
console.log(arguments);
arguments = Array.from(arguments);
console.log(arguments); //数组
}
toArray(1, 2, 3, 4, 5, 6);
复制代码
2....
展开运算符
function toArray(...arguments) {
console.log(arguments);//[1, 2, 3, 4, 5, 6]
}
toArray(1, 2, 3, 4, 5, 6);
复制代码
3.手动循环
function toArray() {
let args = [];
for (let i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
console.log(args); // [1, 2, 3, 4, 5, 6]
}
toArray(1, 2, 3, 4, 5, 6);
复制代码
4.arguments具备和数组类似的结构,所以操作数组的一些代码(例如:循环)也同样适用于arguments;
如果我们让Array原型上的内置方法执行,并且让方法中的this变为我们要操作的类数组,那么就相当于我们在“借用数组原型上的方法操作类数组”
,让类数组也和数组一样可以调用这些方法实现具体的需求:
function toArray() {
// let args = Array.prototype.slice.call(arguments);
let args = [].slice.call(arguments);
console.log(args); //[1, 2, 3, 4, 5, 9, 6, 7]
// 借用array.prototype.forEach,让forEach中的this指向arguments
[].forEach.call(arguments, (item) => {
console.log(item);
});
}
toArray(1, 2, 3, 4, 5, 9, 6, 7);
复制代码
改变this指向:
function func(x, y) {
console.log(this, x, y); //改了之后就是obj
}
Function.prototype.myBind = function () {
//将参数拆解为数组,并把this指向数组原型
const args = Array.prototype.slice.call(arguments);
//获取this(数组第一项)
const t = args.shift();
const self = this;
//返回一个this已经改变了的函数
return function () {
return self.apply(t, args);
};
};
let obj = {};
document.body.onclick = func.myBind(obj, 10, 20, 30);
复制代码
第三部分:补充一些阶段训练题
练习一:
var x = 3;
var obj = {
x: 5,
};
//自执行函数this是window
obj.fn = (function () {
//自己执行window.x=window.x*(++x)=3*4=12
this.x *= ++x;
return function (y) {
this.x *= ++x + y;
console.log(x);
};
})();
var fn = obj.fn;
obj.fn(6);
fn(4);
console.log(obj.x, x);
/*-------------------------------------------------------------------------------------------*/
//解析过程
ec(g)全局上下文:
AF1对象堆{
x: 5,==>x=95
}
Bf1函数堆{
形参:y
this.x *= ++x + y;
console.log(x);
}
变量提升:x=3=>x=12=>x=13=》x=14=》x=234 obj=Af1对象堆
代码执行:
obj.fn =()() =Bf1
fn = obj.fn=Bf1
obj.fn(6);
ec(Ofn1)==ec(bf1)私有上下文:{
原型链:ec(fn1)-->ec(g)
初始化this:this-->obj
形参赋值:y=6
变量提升:
代码执行:this.x *= ++x + y;//obj.x=obj.x*(++x+y)=5*(13+6)=19*5=95
console.log(x);//打印x=13
}
fn(4);
ec(fn2)=bf1(fn2)私有上下文:{
原型链:ec(fn2)-->ec(g)
初始化this:this-->window
形参赋值:y=4
变量提升:
代码执行:this.x *= ++x + y;//window.x=window.x*(++x+y)=13*(14+4)=234
console.log(x);//打印x=234
}
console.log(obj.x, x);//打印95 234
复制代码
练习二:
let obj = {
fn: (function () {
return function () {
console.log(this);
};
})(),
/*
fn:function(){
console.log(this);
}
*/
};
obj.fn(); //this指向obj打印obj
let fn = obj.fn;
fn(); //window
复制代码
练习三:
var fullName = "language";
var obj = {
fullName: "javascript",
prop: {
getFullName: function () {
return this.fullName;
},
},
}; //af0对象堆
console.log(obj.prop.getFullName()); //obj.prop.fullname=undefined
var test = obj.prop.getFullName; //test=function(){ return this.fullName}
console.log(test()); //this是全局打印language
复制代码
练习四:
var name = "window";
var Tom = {
name: "Tom",
show: function () {
console.log(this.name);
},
wait: function () {
// this:Tom
var fun = this.show;
/*
fun=function () {
console.log(this.name);
},
*/
fun(); // this:window => window.name => 'window'
},
};
Tom.wait(); //第一步:this指向tom
复制代码
练习题五:
window.val = 1;
var json = {
val: 10,
dbl: function () {
this.val *= 2;
},
};
json.dbl();
/*
this:json
json.val = json.val * 2 = 20
*/
var dbl = json.dbl;
dbl();
// this:window
// window.val = window.val * 2 = 2
json.dbl.call(window);
// this:window
// window.val = window.val * 2 = 4
alert(window.val + json.val); //=>'24'
复制代码
练习六:
(function () {
var val = 1; //val=2 变量 【注意,这个变量不是全局的,而是匿名函数私有上下文中的】
var json = {
val: 10, // 属性 【不是变量】
dbl: function () {
// this:json
val *= 2; // val=val*2=1*2=2
},
};
json.dbl(); //函数执行形成栈他的上级上下文是不是json,把val=2
alert(json.val + val); //=>'12'
})();
复制代码
练习七:箭头函数与普通函数的区别
箭头函数中没有arguments
箭头函数中没有this,(箭头函数的this看外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this, 如果没有,则this是window。)
箭头函数没有构造函数(没有原型prototype),所以不能被new执行
复制代码
·