文章目录
JavaScript中this的指向
1. 普通函数调用
严格模式下,this会绑定到 undefined ,只有函数运行在非严格模式下,默认绑定才能绑定到全局对象 window。
-
非严格模式
非严格模式中,普通函数中的this绑定到全局对象,类似于:
fn.call(window); fn.apply(window);
// 非严格模式 var name = 'name1'; var fn = function(){ console.log(this === window); // true console.log(this.name); // 'name1' } fn();
你可能会误以为fn()是window调用的,所以this指向window。
事实上,并非如此。
上面代码中这是因为在ES5中用var声明的
,全局变量是挂载在顶层对象(浏览器是window)中,
所以fn()
相当于是window.fn()
,name
相当于window.name
使用ES6的let关键字来声明:
// 非严格模式 let name = 'name2'; let fn = function(){ console.log(this === window); // true console.log(this.name); // undefined window.name = undefined } fn()
这个例子中
let
没有给顶层对象中(浏览器是window)添加属性,window.name
和window.fn
都是undefined
。
.
-
严格模式
严格模式中,普通函数中的 this 则表现为 undefined,类似于:
fn.call(undefined); fn.apply(undefined);
// 严格模式 'use strict' var name = 'window'; var fn = function(){ console.log(this); // undefined console.log(this.name); // 报错 因为this是undefined } fn();
2. 对象中的函数(方法)调用模式
this指向最后调用它的对象,也就是看它执行的时候是谁调用的
var name = 'window';
var fn = function(){
console.log(this.name);
}
var student = {
name: 'xixi',
fn: fn,
other: {
name: 'other',
fn: fn,
}
}
student.fn(); // 'xixi' => fn方法被student调用,this指向student
student.other.fn(); // 'other' => fn方法被other调用,this指向other
// 用call类比则为:
student.fn.call(student); // 'xixi' => 通过call改变this指向,指向到student
// 用call类比则为:
student.other.fn.call(student.other); // 'other' => 通过call改变this指向,指向到student中的other
但往往会有以下场景,把对象中的函数赋值成一个变量了。
这样其实又变成普通函数了,所以使用普通函数的规则(默认绑定)。
var studentFn = student.fn;
studentFn(); // 'window'
// 用call类比则为:
studentFn.call(undefined); // 'window'
3. call、apply、bind 调用模式
JavaScript中call,apply,bind方法的总结
call()、apply()、bind() 都是用来重定义 this 这个对象的(改变this指针)
-
三者都可以改变函数的 this 对象指向。
-
三者第一个参数都是 this 要指向的对象,如果如果没有这个参数或参数为 undefined 或 null,则默认指向全局 window。
-
三者都可以传参,apply参数是数组,call、bind参数是arguments对象,且 apply 和 call 是一次性传入参数,而 bind 可以分为多次传入。
-
bind 是返回绑定 this 之后的函数,便于稍后调用;apply 、call 则是立即执行 。
-
bind()会返回一个新的函数,如果这个返回的新的函数作为构造函数创建一个新的对象,那么此时 this 不再指向传入给 bind 的第一个参数,而是指向用 new 创建的实例
fun.call(thisArg, arg1, arg2, ...)
fun.apply(thisArg, [arg1, arg2, ...])
fun.bind(thisArg, arg1, arg2, ...)()
在fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。
根据参数thisArg的描述,可以知道,call就是改变函数中的this指向为thisArg,并且执行这个函数,这也就使JS灵活很多。
严格模式下,thisArg是原始值是值类型,也就是原始值。不会被包装成对象。举个例子:
var doSth = function(name){
console.log(this);
console.log(name);
}
doSth.call(2, 'xixi'); // Number{2}, 'xixi' this指向被包装成对象的2
var doSth2 = function(name){
'use strict';
console.log(this);
console.log(name);
}
doSth2.call(2, 'xixi'); // 2, 'xixi' this指向原始值2
4. 构造函数new调用模式
通过构建函数new关键字生成一个实例对象,此时this指向这个实例对象
function Student(name){
this.name = name;
console.log(this); // Student {name: 'xixi'} this指向这个实例对象
}
var result = new Student('xixi'); // new关键字改变了this的指向,指向这个实例对象
console.log(result); // Student {name: 'xixi'}
console.log(result.name); // 'xixi'
注意:
-
new过程遇到return一个对象,此时this指向为返回的对象
function Student(name){ this.name = name; // return function fn(){}; // 如果返回函数fn,则result是fn(){} return {}; // 如果返回对象{},则result是对象{} } var result = new Student('xixi'); console.log(result); // {} 返回对象是{},则result就是{} console.log(result.name); // undefined
-
注意的是null虽然也是对象,但是此时new仍然指向实例对象
function Student(name){ this.name = name; return null; } var result = new Student('xixi'); console.log(result); // Student {name: 'xixi'} 此时new仍然指向实例对象 console.log(result.name); // xixi
5. 原型链中的调用模式
function Student(name){
this.name = name;
}
var s1 = new Student('xixi');
Student.prototype.fn = function(){
console.log(this.name);
}
s1.fn(); // 'xixi' fn方法被s1调用,此时fn中的this指向s1
会发现这个似曾相识。这就是对象上的方法调用模式。自然是指向生成的新对象。 如果该对象继承自其它对象。同样会通过原型链查找。 上面代码使用 ES6中class写法则是:
class Student{
constructor(name){
this.name = name;
}
fn(){
console.log(this.name);
}
}
let s1 = new Student('xixi');
s1.fn();
ES6的class也是通过构造函数模拟实现的,是一种语法糖。
6. ⚡️箭头函数调用模式
箭头函数不绑定自己的 this,它捕获其所在上下文的 this 值,作为自己的 this 值。无论箭头函数在哪里被调用,或者它如何被调用,它的 this 都由创建时决定。
箭头函数的特点:
- 没有自己的this、super、arguments和new.target绑定。
- this由创建时决定,指向定义时的上下文,通常是全局对象(在浏览器中是 window)
- 不能使用new来调用。
- 没有原型对象。
- 不可以改变this的绑定。
- 形参名称不能重复。
箭头函数中没有this绑定,必须通过查找作用域链来决定其值。
如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this,否则this的值则被设置为全局对象。 比如:
var name = 'window';
var student = {
name: 'xixi',
fn: function(){
var arrowFn = () => { // 1.箭头函数被非箭头函数包含
console.log(this.name); // 则this绑定的是最近一层非箭头函数的this
}
arrowFn();
},
arrowFn2: () => {// 2.箭头函数没有被非箭头函数包含
console.log(this.name); // 因为箭头函数的 this 指向定义时的上下文,通常是全局对象(在浏览器中是 window)
}
};
student.fn(); // 'xixi' this指向student
student.arrowFn2(); // 'window' this指向window
其实就是相当于箭头函数外的this是缓存的该箭头函数上层的普通函数的this。
如果没有普通函数,则是全局对象(浏览器中则是window)。
也就是说无法通过call、apply、bind绑定箭头函数的this(它自身没有this)。
而call、apply、bind可以绑定缓存箭头函数上层的普通函数的this。比如:
var student = {
name: 'xixi',
fn: function(){
console.log(this.name);
return () => {
console.log('arrowFn:', this.name);
}
}
}
var person = {
name: 'person'
}
student.fn().call(person); // 'xixi' 'arrowFn:' 'xixi'
student.fn.call(person)(); // 'person' 'arrowFn:' 'person'
优先级
new 调用
> call、apply、bind 调用
> 对象上的函数调用
> 普通函数调用
总结
如果要判断一个运行中函数的 this 绑定, 就需要找到这个函数的直接调用位置。 找到之后
就可以顺序应用下面这四条规则来判断 this 的绑定对象:
- new 调用:绑定到新创建的对象,注意:显示return函数或对象,返回值不是新创建的对象,而是显式返回的函数或对象。
- call、apply、bind调用:严格模式下,绑定到指定的第一个参数。非严格模式下,null和undefined,指向全局对象(浏览器中是window),其余值指向被new Object()包装的对象。
- 对象上的函数调用:绑定到那个对象。
- 普通函数调用: 在严格模式下绑定到 undefined,否则绑定到全局对象。
ES6 中的箭头函数:不会使用上文的四条标准的绑定规则, 而是根据当前的词法作用域来决定this, 具体来说, 箭头函数会继承外层函数,调用的 this 绑定( 无论 this 绑定到什么),没有外层函数,则是绑定到全局对象(浏览器中是window)。 这其实和 ES6 之前代码中的 self = this 机制一样。
DOM事件函数:一般指向绑定事件的DOM元素,但有些情况绑定到全局对象(比如IE6~IE8的attachEvent)。
1. 函数调用
this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的
function a() {
var name = 'Tom'
console.log(this.name); //undefined (window下没有name这个属性)
console.log(this); //window
}
a(); // 相当于window.a(),也就是window调用了a()方法,所以a方法中的this指向了window
var b = {
say:{
name: 'Tom',
fn:function(){
console.log(this.name); //Tom (say下有name这个属性)
}
}
};
b.say.fn(); // say调用了fn方法,所以fn方法中的this指向了say
var c = {
name: 'Tom',
say:{
fn:function(){
console.log(this.name); //undefined (say下没有name这个属性)
}
}
};
c.say.fn(); // say调用了fn方法,fn方法中的this指向了say,但say中没有name属性
var name = 'Jon'
var d = {
name: 'Tom',
say:{
fn:function(){
console.log(this.name); // Jon (this指向window)
}
}
};
var j = d.say.fn; //fn虽然被say引用,但是没有被执行调用,所以this不是指向的say
j() // 而是指向调用的j()方法的window
var name = "The Window";
var object = {
name: "The Object",
getNameFunc: function () {
return function () {
return this.name;
};
}
};
alert(object.getNameFunc()()); //The Window
//匿名函数的自我执行,没有被上级对象调用,所以this指向window
var name = "The Window";
var object = {
name: "The Object",
getNameFunc: function () {
var that = this;
return function () {
return that.name;
};
}
};
alert(object.getNameFunc()()); //The Object
//在getNameFunc方法中将this绑定到that对象上,而that属于getNameFunc函数的内部变量,而这个函数的执行域是object。
//所以结果是"The Object"
var obj = {
say: function () {
setTimeout(function () {
console.log(this == window)
});
}
}
obj.say(); //true
.
2. 箭头函数
箭头函数的this指向是根据当前的词法作用域来决定。
具体来说, 箭头函数会继承外层函数,调用的 this 绑定( 无论 this 绑定到什么),没有外层函数,则是绑定到全局对象(浏览器中是window)。
var obj = {
say: function () {
setTimeout(() => {
console.log(this)
});
}
}
obj.say(); // this指向obj
// 箭头函数没有自己的this, 它的this是继承而来;
// 默认指向在定义它时所处的对象(宿主对象),而不是执行时的对象, 定义它的时候,可能环境是window
构造函数中的this
function Fn(){
this.user = "bing";
}
var a = new Fn(); //new关键字可以改变this的指向,将这个this指向实例化对象a
console.log(a.user); //bing
为什么this会指向a?首先new关键字会创建一个空的对象,然后会自动调用一个函数apply方法,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。
如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。
function fn() {
this.user = 'bing';
return {user:'xia'};//返回的是对象
}
var a = new fn;
console.log(a.user); //xia
function fn() {
this.user = 'bing';
return function(){};//返回的是对象
}
var a = new fn;
console.log(a.user); //undefined
function fn() {
this.user = 'bing';
return false; //返回的是布尔值
}
var a = new fn;
console.log(a.user); //bing
function fn() {
this.user = 'bing';
return 1; //返回的是数字
}
var a = new fn;
console.log(a.user); //bing
function fn() {
this.user = 'bing';
return 'xia'; //返回的是字符串
}
var a = new fn;
console.log(a.user); //bing
function fn() {
this.user = 'bing';
return undefined; //返回的是undefined
}
var a = new fn;
console.log(a.user); //bing
function fn() {
this.user = 'bing';
return null; //返回的是null
}
var a = new fn;
console.log(a.user); //bing
改变this指向
我们在js中可以使用document.write()
方法向网页中输入文本内容。如
document.write("test");
网页中就会被加入’test’文本。
但是如果我们像下面这么写呢?
var myWrite = document.write;
myWrite("test"); //报错
上面的代码首先是我将document.write方法赋值给myWrite方法。然后使用myWrite()方法向网页中输入文本内容。但是这里会报错,为什么呢?
原因就在这个write方法的this指针的域被改变啦!
document.write()方法的this是指向document的,所以可以向网页中输入文本内容。
但是我们将document.write方法赋值给myWrite对象,然后在调用myWrite()方法。调用myWrite()方法的对象域是全局变量window,相当于window.myWrite()。此时this指向window,而不指向document.所以会报错。
遇到上面的this指向被改变了,该怎么办呢?
这时候就要设置this的指针指向了,JavaScript有三个方法bind()、call()、apply()。
1.使用bind方法
a.bind(b); 就是将a()方法的this指针的作用域绑定到b对象的作用域上,也就是现在a的this指针的作用域就是b
如是上面的代码就可以改成:
var myWrite = document.write;
myWrite.bind(document)("test"); //此时的this指针指向了bind()的document,网页中正常输出test。
2.使用call方法
call(a, b);
: a是当前方法需要指向的域,后面的b是当前方法的参数,可以传多个参数,多个参数直接用逗号隔开即可,如 :call(a, b, c, d);
, b, c, d都是方法的参数(arguments)。
var myWrite = document.write;
myWrite.call(document, "test"); //此时的this指针指向了bind()的document,网页中正常输出test。
3.使用apply方法
apply(a, b);
:a是当前方法需要指向的域,后面的b是当前方法的参数,可以传多个参数,多个参数需要使用数组来传入。如apply(a, [b, c, d]);
,b、c、d为参数(数组)。
var myWrite = document.write;
myWrite.apply(document, "test"); //此时的this指针指向了bind()的document,网页中正常输出test。