在JavaScript中,this的指向是调用时决定的,而不是创建时决定的。
一、全局上下文中
在全局上下文中,this
为 window
对象,用 var
声明的变量以及用 function
声明的函数都会挂载在 window
上,let
和 const
声明的变量则不会。
console.log(this);// Window{}
console.log(this === window);// true
var a = 10;
let b = 20;
const c = 30;
console.log(this.a === window.a);// true
console.log(this.b);// undefined
console.log(this.c);// undefined
二、函数上下文中
在函数内部,this
的值取决于函数被调用的方式。
1、普通函数的this指向
在没有开启严格模式的情况下直接在全局调用函数,函数内 this
指向 window
,开启严格模式为则 undefined
。
// 不开启严格模式
function foo(){
console.log(this);// Window{}
return this;
}
console.log(foo() === window); // true
/** ----------------- 分割线 ------------------- */
// 开启严格模式
"use strict";
function foo(){
console.log(this);// undefined
return this;
}
console.log(foo() === window); // false
2、箭头函数的this指向
箭头函数会捕获其所在上下文的this值,作为自己的this值。
function foo() {
console.log(this);// obj
setTimeout(()=>{
console.log(this.a);// 2
console.log(this === obj);// true
},100)
}
var obj = {
a: 2
}
foo.call(obj);
3、对象内部函数的this指向
var person = {
age: 18,
getAge: function() {
console.log('getAge...', this);
},
getName: a => {
console.log('getName...', this, a);
}
}
person.getAge();// person
person.getName();// window
var getAge = person.getAge;
var getName = person.getName;
getAge();// window
getName();// window
getAge.call(person);// person
getName.call(person, 1);// window 箭头函数不能通过bind,call,apply改变this指向,但是参数可以传过去
4、实例对象的this指向
1、构造函数上面的箭头函数this指向始终指向实例对象(箭头函数会捕获其所在上下文的this值,作为自己的this值),普通函数指向调用者
2、构造函数原型对象上的方法,普通函数this指向调用者,箭头函数this指向window
// 构造函数上面的箭头函数this指向始终指向实例对象,普通函数指向调用者
function Person () {
this.getName = (name = 'lsyana') => {
console.log(name, this);// this始终指向实例对象
};
this.getFriend = function(friend = 'lucy') {
console.log(friend, this);
};
}
// 构造函数原型对象上的方法,普通函数this指向调用者,箭头函数this指向window
Person.prototype.getAge = function (age = 18) {
console.log(age, this);
}
Person.prototype.sayHi = (msg = 'hahahah~') => {
console.log(msg, this);
}
var person = new Person();
person.getName();// 'lsyana', Person {getName: ƒ, getFriend: ƒ}
person.getFriend();// 'lucy', Person {getName: ƒ, getFriend: ƒ}
person.getAge();// 18, Person {getName: ƒ, getFriend: ƒ}
person.sayHi();// 'hahahah~', Window{}
console.log('---------分割线---------');
var {getName, getFriend, getAge, sayHi} = person;
getName();// 'lsyana', Person {getName: ƒ, getFriend: ƒ}
getFriend();// 'lucy', Window{}
getAge();// 18, Window{}
sayHi();// 'hahahah~', Window{}
console.log('---------分割线---------');
var p1 = {parent: 'p1', getName, getFriend, getAge, sayHi};
p1.getName();// 'lsyana', Person {getName: ƒ, getFriend: ƒ}
p1. getFriend();// 'lucy', {parent: 'p1', getName: ƒ, getFriend: ƒ, getAge: ƒ, sayHi: ƒ}
p1.getAge();// 18, {parent: 'p1', getName: ƒ, getFriend: ƒ, getAge: ƒ, sayHi: ƒ}
p1.sayHi();// 'hahahah~', Window{}
new过程发生了什么?
通过new操作符调用构造函数的5个步骤:
- 创建一个新对象
- 将新对象内部的
[[Prototype]]
特性被赋值为构造函数的prototype
属性 - 构造函数内部的
this
被赋值为这个新对象(即this
指向新对象) - 执行构造函数内部的代码(给新对象添加属性)
- 如果构造函数返回对象,则返回该对象;否则,返回刚创建的新对象(空对象)
function _new(Fn, ...args) {
// Object.create(现有的对象) 使用现有的对象作为新对象的原型
// 1、新建一个对象,并将构造函数的原型作为新对象的原型,继承构造函数原型上的方法
var obj = Object.create(Fn.prototype);
// 2、将构造函数的this指向新的对象,执行构造函数内部代码并拿到返回值
var res = Fn.apply(obj, args);
// 3、构造函数有返回则返回构造函数的返回值,没有则返回新对象obj
return res instanceof Object ? res : obj;
}
/** -------------- 也可以写成下面这样子 -------------- */
function _new(Fn, ...args) {
// 1、新建一个对象
var obj = {};
// 2、新对象的原型指针指向构造函数的原型,继承构造函数原型上的方法
obj.__proto__ = Fn.prototype;
// 3、将构造函数的this指向新的对象,执行构造函数内部代码并拿到返回值
var res = Fn.apply(obj, args);
// 4、构造函数有返回则返回构造函数的返回值,没有则返回新对象obj
return res instanceof Object ? res : obj;
}
5、改变函数的this指向
Function.prototype.call(thisArg, arg1, arg2, …)
在使用一个指定的 this
值和若干个指定的参数值的前提下调用某个函数或方法。
call实现:
- 将函数设为对象的属性
- 使用
obj.fn()
的方式执行该函数并且拿到返回值(调用fn
的是obj,此时this
指向obj
) - 删除对象上该函数
var obj = {
age: 18
}
function getAge(a, b) {
console.log(this, this.age, a, b);
}
Function.prototype._call = function (context, ...args) {
// this为调用_call的函数getAge
console.log('原来的this > this:', this);
// context为传入的对象
console.log('传入的对象context > context:', context);
// 1. 当传入的this指向对象为null/undefined时,this指向window
context = context || window;
// 2. 给传入的对象上添加一个函数,并且赋值为调用的函数getAge
context.fn = this;
// 3. 调用getAge,此时this为调用它的对象context(也就是传入的obj)
context.fn(...args);
// 4. 上面的操作会给传入的对象添加了一个多余的属性fn,用完删掉它,就跟原来的一样了
delete context.fn;
};
getAge._call(obj, 1, 2);// {age: 18, fn: ƒ} 18 1 2
getAge._call(null, 1, 2);// Window {window: Window, self: Window, document: document, name: '', location: Location, …} undefined 1 2
Function.prototype.apply(thisArg[, argsArray])
传入一个指定的 this
对象值和一个有多个参数组成的数组,将 this
指向对象并执行函数。
apply实现:
Function.prototype._apply = function (context, args) {
context = context || window;
context.fn = this;
var res = context.fn(...args);
delete context.fn;
return res;
};
Function.prototype.bind(thisArg[, arg1[, arg2[, …]]])
创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
bind实现:
Function.prototype._bind = function (context, ...args) {
context = context || window;
context.fn = this;
return function () {
return context.fn(...args);
}
}