变量this的创建
结论:this的指向是动态的,取决于执行顺序而不是定义的作用域链。this指向的时调用它的对象中最近的那一个。
注意:本篇中的函数都是普通函数,没有使用到箭头函数,因为只有普通函数的执行环境中才有this这个变量被创建,才是指向离他最近的那一个
指向
一、全局对象
在函数中,this指向全局对象
function a() {
console.log(this); //window
this.newvariable = 'hello';//改变全局对象的属性
}
var b = function() {
console.log(this); //window
}
a();
b();
console.log(newvariable); //hello
a和b的调用,都是window对象,a()可以写成window.a();b()可以写成window.b();
无论是函数的statement还是expression,this都指向调用它们的window
二、上级对象
对象的方法属性中,this指向这个对象
var name = 'windowName
var c = {
name: 'The c object',
log: function() {
this.name = 'Updated c object';//改变c的属性
console.log(this.name);//c
}
}
c.log(); // 'The c object'
log的调用实际上时window.c.log();this指向调用log的,离它最近的c对象
在setTimeout中不合预期
var c = {
name: 'The c object',
log: function () {
console.log(this.name) //c
},
}
c.log() // The c object
setTimeout(c.log, 1000) // windowName or undefined
this的指向收到执行环境栈的影响,在setTimeout中会有一个新的执行环境,所以在浏览器中指向window,在node中指向timeOut对象
解决方法: 加一个函数包装/绑定this
setTimeout(()=>{c.log()}, 1000);
setTimeout(function(){c.log()}, 1000)
setTimeout(c.log().bind(c), 1000)
三、没有调用它的对象时
对象方法属性的内部函数,this指向全局对象
var c = {
name: 'The c object',
log: function() {
this.name = 'Updated c object';//改变对象c的属性
console.log(this);
var setname = function(newname) {
this.name = newname; //改变全局对象的属性
}
setname('Updated again! The c object');
console.log(self);
}
}
c.log();
log被c调用(c.log();),log中this指向c对象;
而setname没有调用它的对象(setname('Updated again! The c object');),setname中this指向window
这里很容易出错,所以经常会用到var self = this;
在进入内部函数之前确定下来this的指向,后面都用self代替thisthis会有多个,但self只有一个,它指向c这个对象:
var c = {
name: 'The c object',
log: function() {
var self = this;//self就是c
self.name = 'Updated c object';
console.log(self);
var setname = function(newname) {
self.name = newname; //在当前执行环境中没能找到self对象,取outer environment中找,找到了,并确定下来就是c
}
setname('Updated again! The c object');
console.log(self);
}
}
c.log();
改变指向
用bind/apply/call改变
var person = {
firstname: 'John',
lastname: 'Doe',
getFullName: function() {
var fullname = this.firstname + ' ' + this.lastname;
return fullname;
}
}
var logName = function(lang1, lang2) {
console.log('Logged: ' + this.getFullName());//Logged: John Doe
console.log('Arguments: ' + lang1 + ' ' + lang2);//Arguments: en undefined
console.log('-----------');
}
var logPersonName = logName.bind(person);
logPersonName('en');
logName.bind(person)将函数logName中this指向person对象,所以logPersonName('en')调用时this指向的不是window对象,而是person这个对象
bind的实际操作:logName.bind(person)将 logName这个函数复制一份,将他作为一个方法,成为Person的一部分,logName函数中的this,就指向了Person
bind/apply/call是所有函数都有三个方法的(存在于函数的原型中)bind是将函数绑到对象上,apply和call是将函数绑到对象上并执行这个函数,区别在于apply在对象后面的参数写成数组的形式(logName.apply(person,['en','em'])),call是分开写(logName.call(person,'en','em'))
箭头函数
函数在执行时会创建一个函数执行环境,普通函数的执行环境中会有this,arguments,而箭头函数没有。同时由于箭头函数没有this变量,就无法作为构造函数,也没有prototype对象。
没有this
箭头函数中的this永远指向定义时(不是执行时)作用域链最近的那个this,没有办法通过apply/bind/call直接修改箭头函数中this(因为根本就没有)但可以通过修改外层函数中this指向间接修改。
var id = 'Global';
function fun1() {
// setTimeout中使用普通函数
setTimeout(function fun3(){
console.log(this.id);
}, 2000);
}
function fun2() {
// setTimeout中使用箭头函数
setTimeout(() => {
console.log(this.id);
}, 2000)
}
fun1.call({id: 'Obj'}); // 'Global'
fun2.call({id: 'Obj'}); // 'Obj'
fun3作用域链的上一层是fun1,fun1.call({id: 'Obj'}); fun3有自己的this,所以即使修改了fun1中this的指向,但对fun3没有影响。定时器的作用使它是被全局/定时器调用的,this指向全局。
箭头函数的作用域链上一层是fun2,由于箭头函数没有this,它的this来自fun2,fun2.call({id: 'Obj'});修改了fun2中this的指向。
无法直接修改this
var id = 'Global';
// 箭头函数定义在全局作用域
let fun1 = () => {
console.log(this.id)
};
fun1(); // 'Global'
// this的指向不会改变,永远指向Window对象
fun1.call({id: 'Obj'}); // 'Global'
不能作为构造函数使用
let Fun = (name, age) => {
this.name = name;
this.age = age;
};
// 报错
let p = new Fun('cao', 24);
没有arguments
arguments是一个类数组对象,不是数组
类数组对象:可以象数组一样有下标去访问属性,并且有length属性,但其proto是Object.prototype,不是Array.prototype 所以注意:
- arguments无法调用在Array.prototype上的方法,如concat
- concat函数可以接收多个参数,当参数是数组时,会拼接数组,当参数是非数组时,相当于把参数push,所以[].concat(arguments)得到的数组长度只有1
function Person(...destruction) {
console.log(arguments) // Arguments(3) [1, 2, 3, callee: (...), Symbol(Symbol.iterator): ƒ]
let array = []
console.log(array.concat([4, 5, 6])) // [4, 5, 6]
console.log(array.concat(4)) // [4]
console.log(array.concat(4, 5)) // [4, 5]
console.log(array.concat(arguments)) // [Arguments(3)] !!!!!
console.log(array.concat(destruction)) // [1, 2, 3]
}
Person(1, 2, 3)
const ArrowPerson = () => {
console.log(arguments) // undefined
}
function Person() {
console.log(arguments) // Arguments(2) ["1", "2", callee: ƒ, Symbol(Symbol.iterator): ƒ]
}
ArrowPerson('1', '2')
Person('1', '2')