相关知识点
- 函数调用
this的定义this的指向- 如何改变
this的指向
函数调用
JS(ES5)中有三种函数调用形式,分别为以下几种:
fn(p1, p2)
obj.child.method(p1, p2)
fn.call(context, p1, p2)
第三种形式才是正常的调用形式
fn.call(context, p1, p2)
其它两种都是语法糖,可以等价的变为 call 形式
fn(p1, p2)等价于fn.call(undefined, p1, p2);obj.child.method(p1, p2)等价于obj.child.method.call(undefined, p1, p2);
var a = 5;
var obj = {
a: 10,
foo: function () {
console.log(this.a);
}
};
let bar = obj.foo;
obj.foo();
bar();
obj.foo()转化为call的形式为obj.foo.call(obj),所以this指向了obj,最后this.a输出为 10;bar()转化为call的形式为bar.call(),由于没有传context,如果在浏览器环境下this指向Window对象,如果是在Node.js环境中运行,this指向global对象,在浏览器中输出为 5;在Node.js中输出为undefined;
this 的定义
this 就是一个对象,不同情况下 this 的指向不同。
this 的几种指向情况
全局环境(默认绑定)
在全局执行环境中(在任何函数的外部),this 都是指向全局对象,在浏览器环境下,window 对象即是全局对象。
var a = 1;
let b = 2; // let和const定于的数据不会绑定在window上
console.log(window.a); // 1
console.log(window.b); // undefined
this.c = 3;
console.log(c); // 3
console.log(window.c); // 3
如果使用 let 或者 const 定义数据,是不会绑定在 window 上的。
默认绑定的另一种情况(setTimeOut/setInterval)
在函数中一函数作为参数传递,例如 setTimeOut 和 setInterval 等,这些函数中传递的函数中的 this 指向,在非严格模式下指向的是全局对象。
var name = "tom";
var person = {
name: "我是person",
sayHi: sayHi
};
function sayHi() {
console.log("Hello,", this.name);
}
setTimeout(function () {
console.log(this);
person.sayHi();
sayHi();
}, 200);
输出结果为:
Window {window: Window, self: Window, document: document, name: "tom", location: Location, …}
Hello, 我是person
Hello, tom
对象调用
this 指向该对象(前面谁调用 this 就指向谁),和声明在哪里无关。
var obj = {
name: "Tom",
age: "21",
print: function () {
console.log(this);
console.log(this.name + " - " + this.age);
}
};
obj.print();
根据上面所讲,obj.print() 可以转化为 obj.print.call(obj),所以函数中的 this 指向 obj 对象,最后输出结果为:Tom - 21
当有多层对象嵌套调用某个函数的时候,如对象.对象.函数,this 指向的是最后一层对象。
function sayHi() {
console.log(this);
console.log("Hello,", this.name);
}
var person = {
name: "tom",
sayHi: sayHi
};
var person1 = {
name: "jack",
friend: person
};
person1.friend.sayHi();
console.log(person1.friend);
输出结果为:
{name: "tom", sayHi: ƒ}
Hello, tom
{name: "tom", sayHi: ƒ}
直接调用的函数
直接调用的函数 this 指向的是全局对象。
function print() {
console.log(this);
}
print();
[] 语法中的 this
我们先看一个简单的 demo:
function fn() {
console.log(this);
console.log(this === arr); // true
}
function fn1() {
console.log(this);
}
var arr = [fn, fn1];
arr[0]();
我们可以把 arr[0]() 想象成 arr.0(),虽然语法上有错误(先忽略错误),但是形式可以和 obj.child.method(p1, p2) 相对应,最后转换成 arr[0]() => arr.0() => arr.0.call(arr) 的形式,这样就可以理解 this 指向 arr 了。
new 构造函数中的 this
this永远指向新创建的对象;- 如果构造函数中有
return- 如果
return的值是对象,this指向返回的对象; - 如果
return的值不是对象,则this指向保持原有的规则;
- 如果
null比较特殊,null的数据类型为对象,但是this指向保持原有的规则;
题目一
function Person(name, age) {
this.name = name;
this.age = age;
console.log(this); // Person {name: "Tom", age: 22}
}
new Person("Tom", 22);
题目二
function Fn() {
this.num = 10;
}
Fn.num = 20; // num为Fn构造函数的静态属性
Fn.prototype.num = 30; // num为Fn构造函数的实例属性
Fn.prototype.method = function () {
console.log(this);
console.log(this.num);
};
var prototype = Fn.prototype;
var method = prototype.method;
let fn = new Fn();
console.log(fn); // Fn {num: 10}
console.log(Fn.num); // 输出的是静态属性 20
fn.method(); // this 指向 Fn,this.num => 10
prototype.method(); // this 指向 prototype,this.a => 30
method(); // this 指向全局window,this.a => undefined
题目三
function Fn() {
this.num = 10;
// return "";
// return {};
// return {
// num: 20
// };
// return null;
return undefined;
}
var fn = new Fn();
console.log(fn.num);
- 若
return "",结果输出为:10; - 若
return {},结果输出为:undefined; - 若
return { num: 20 },结果输出为:20; - 若
return null,结果输出为:10; - 若
return undefined,结果输出为:10;
箭头函数中的 this
- 箭头函数没有
this和arguments; - 箭头函数中的
this在定义的时候就已经决定了,后期使用call、apply、bind都不能改变this的指向; - 由于箭头函数没有单独的
this值,所以箭头函数中的this与声明所在的上下文相同; - 调用箭头函数的时候,不会隐士的调用
this参数,而是定义时的函数继承上下文; - 箭头函数没有构造函数;
- 箭头函数与正常的函数不同,箭头函数没有构造函数
constructor,因为没有构造函数,所以也不能使用new来调用,如果我们直接使用new调用箭头函数,会报错;
- 箭头函数与正常的函数不同,箭头函数没有构造函数
let fun = () => {};
let funNew = new fun();
// 报错内容 TypeError: fun is not a constructor
- 箭头函数没有原型;
- 原型
prototype是函数的一个属性,但是在箭头函数中不存在prototype;
- 原型
let fun = () => {};
console.log(fun.prototype); // undefined
let fun2 = function () {};
console.log(fun2.prototype); // {constructor: ƒ}
- 箭头函数中没有
super;- 由于箭头函数中没有原型,连原型都没有,自然也不能通过
super来访问原型的属性,所以箭头函数也是没有super的,不过跟this、arguments、new.target一样,这些值由外围最近一层非箭头函数决定;
- 由于箭头函数中没有原型,连原型都没有,自然也不能通过
- 对象不能形成独立的作用域;
const obj = {
a: () => {
console.log(this);
},
b: function () {
console.log(this);
console.log(this === obj); // true
},
c() {
console.log(this);
console.log(this === obj); // true
}
};
obj.a(); // Window
obj.b(); // {a: ƒ, b: ƒ, c: ƒ}
obj.c(); // {a: ƒ, b: ƒ, c: ƒ}
var length = 10;
function fn() {
console.log(this);
console.log(this.length);
}
var obj = {
length: 5,
method: function (fn) {
fn();
arguments[0]();
}
};
obj.method(fn, 1);
输出结果为:
Window 10
Arguments 2
var name = "tom";
var obj = {
name: "jack",
prop: {
name: "rose",
getName: () => {
return this.name;
}
}
};
console.log(obj.prop.getName());
var test = obj.prop.getName;
console.log(test());
使用 obj.prop.getName() 方式调用情况下,this 与声明所在的上下文相同,也就是 Window,所以 this.name => Window.name,结果为 tom。
输出结果为:
tom,tom
如果把 getName 改成普通函数的话:
var name = "tom";
var obj = {
name: "jack",
prop: {
name: "rose",
getName: function () {
return this.name;
}
}
};
console.log(obj.prop.getName());
var test = obj.prop.getName;
console.log(test());
输出结果为:
rose,tom
自执行函数
什么是自执行函数?自执行函数在我们在代码只能够定义后,无需调用,会自动执行。代码例子如下:
(function () {
console.log(this); // Window
console.log("我是fn");
})();
或者
(function() {
console.log(this); // Window
console.log("我是fn");
}());
但是如果使用了箭头函数简化一下就只能使用第一种情况了,使用第二种情况简化会报错。
// 正确
(() => {
console.log("我是fn");
})();
// 报错,Uncaught SyntaxError: Unexpected token '('
(() => {
console.log("我是fn");
}());
以一个例题解释一下自执行函数中的 this 指向:
function Fn() {
this.name = "tom";
(() => {
console.log(this); // Fn {name: "tom"}
})();
(function () {
console.log(this); // Window
})();
}
let fn = new Fn();
(() => {
console.log(this); // Window
})();
(function () {
console.log(this); // Window
})();
如何改变 this 的指向
我们可以通过调用函数的 call、apply、bind 来改变 this 的指向。
var obj = {
name: "tom",
age: "22",
info: "我叫Tom,22岁"
};
function print() {
console.log(this); // 打印this的指向
console.log(arguments); // 打印传递的参数
}
// 通过 call 改变 this 指向
print.call(obj, 1, 2, 3);
// 通过 apply 改变 this 指向
print.apply(obj, [1, 2, 3]);
// 通过 bind 改变 this 的指向,不会立即执行,会返回一个函数
let fn = print.bind(obj, 1, 2, 3);
fn();
- 共同点:
- 三者都能改变
this指向,且第一个传递的参数都是this指向的对象; - 三者都采用的后续传参的形式;
- 三者都能改变
- 不同点:
call的传参是单个传递的(数组也可以,不报错),而apply后续传参的参数数组形式(传单个值会报错),而bind没有规定,传递值和数组都可以;call和apply函数的执行是立即执行的,而bind函数会返回一个函数,通过调用函数才会执行;
如果使用上边的方法改变箭头函数的 this 指针,会发生什么情况呢?能否进行改变呢?
由于箭头函数没有自己的 this 指针,通过 call() 或 apply() 方法调用一个函数时,只能传递参数(不能绑定 this),他们的第一个参数会被忽略。
var obj = {
name: "tom",
age: "22"
};
const print = (...args) => {
console.log(this); // 都是指向 Window
console.log(args); // [1, 2, 3]
};
// 通过 call 改变 this 指向
print.call(obj, 1, 2, 3);
// 通过 apply 改变 this 指向
print.apply(obj, [1, 2, 3]);
// 通过 bind 改变 this 的指向,不会立即执行,会返回一个函数
let fn = print.bind(obj, 1, 2, 3);
fn();