this-箭头函数
this-->执行上下文所属的作用域
JavaScript 中 this 指的是执行上下文所属的作用域。所以函数中的 this 是在运行时决定的而不是定义时。注意不是上下文,因为上下文还包括其他的
var a1 = 1;
var fn = function () {
let b1 = 1;
debugger;
var b2 = 1;
console.log(a1); // 1
console.log(this.a1); // 1
console.log(this); // window
};
fn();
var obj = {
foo: function () {
var test1 = 1;
debugger;
console.log(this);
console.log(this.bar);
},
test: "hello",
};
obj.foo();
实例代码中,函数体里面使用了变量 a1。该变量由运行环境提供。
现在问题就来了,由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this 就出现了,它的设计目的就是在函数体内部,指代函数所属的作用域。

this 设计和数据结构的关系
因为 function 可以赋值给不同的变量,可以看到 this 表示运行环境,但不是执行上下文,因为 this 不包括 test1
var obj = {
foo: function () {
var test1 = 1;
console.log(this.bar);
},
bar: 1,
};
var fn = obj.foo;
var bar = 2;
debugger;
obj.foo(); // 1
fn(); // 2

- 对于 obj.foo()来说,foo 运行在 obj 环境,所以 this 指向 obj;
- 对于 foo()来说,foo 运行在全局环境,所以 this 指向全局环境。所以,两者的运行结果不一样。
这种解释没错,但是教科书往往不告诉你,为什么会这样?也就是说,函数的运行环境到底是怎么决定的?举例来说,为什么 obj.foo()就是在 obj 环境执行,而一旦 var foo = obj.foo,foo()就变成在全局环境执行?
JavaScript 语言之所以有 this 的设计,跟内存里面的数据结构有关系。
var obj = { foo: 5 };
JavaScript 引擎会先在内存里面,生成一个对象{ foo: 5 },然后把这个对象的内存地址赋值给变量 obj。
也就是说,变量 obj 是一个地址(reference)。后面如果要读取 obj.foo,引擎先从 obj 拿到内存地址,然后再从该地址读出原始的对象,返回它的 foo 属性。
原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来说,上面例子的 foo 属性,实际上是以下面的形式保存的。
注意,foo 属性的值保存在属性描述对象的 value 属性里面。
{
foo: {
[[value]]: 5
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
问题在于属性的值可能是一个函数
var obj = { foo: function () {} };
这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给 foo 属性的 value 属性。
再例如:
var f = function () {};
var obj = { f: f };
// 单独执行
f();
// obj 环境执行
obj.f();
this 指向
箭头函数的 this
继承了它们所在上下文中的 this 值,而不是拥有自己的 this。
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的 this,arguments,super 或 new.target。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。
没有 prototype 属性,没办法连接它的实例的原型链,所以箭头函数就无法作为构造函数。
箭头函数不创建上下文,因此箭头函数的内部 this 与外部的 this 相同。
在 JavaScript 中,函数会创建自己的作用域。这意味着 JavaScript 函数会创建自己的上下文 this. 箭头函数内部的 this 词法做用域由上下文肯定,因此,用 call()或 apply()调用箭头函数时,没法对 this 进行绑定,即传入的第一个参数被忽略。
const fn = (val) => {
var test2 = "2";
var test3 = "3";
// debugger
// window/undefined {val: 1, test2: '2', test3: '3'}
console.log(this, { val, test2, test3 });
};
console.log("fn", fn.prototype); // undefined prototype(原型)是函数才有的,prototype是构造函数的属性,指向属于该构造函数的原型对象。
console.log("fn", fn.__proto__); // fn ƒ () { [native code] } 指向该对象的构造函数的原型对象
fn(1);
// to es5
var fn = function fn(val) {
var test2 = "2";
var test3 = "3";
// debugger;
console.log(undefined, { val: val, test2: test2, test3: test3 });
};
console.log("fn", fn.prototype); // 函数表达式有原型,并且可以作为构造函数
const a = new fn();
console.log("a", a);
fn(1);
/*
* 由于箭头函数的this由外部非箭头函数的this决定,因此,若需要将一个函数作为回调函数去执行,
* 并且不希望函数中的this发生改变,这时用箭头函数最适合不过。如果是普通函数,则需要用bind()进行this绑定。
* */
class Fn {
constructor(name) {
this.consName = name;
}
arrowLog = () => {
console.log(this.consName);
};
normalLog() {
console.log(this.consName);
}
}
const construct = new Fn("arrow");
setTimeout(construct.arrowLog, 1000); // 1s后 => 'arrow'
setTimeout(construct.normalLog, 1000); // 1s后 => 'undefined'
setTimeout(construct.normalLog.bind(construct), 1000); // 1s后 => 'arrow'
// 未解析
const test1 = "hello world";
const add = (a, b) => a + b;
var add2 = function (a, b) {
return a + b;
};
function add3(a, b) {
return a + b;
}
// 经过babel 解析后
("use strict");
var test1 = "hello world";
var add = function add(a, b) {
return a + b;
};
var add2 = function add2(a, b) {
return a + b;
};
function add3(a, b) {
return a + b;
}
函数作为普通函数,在全局环境下进行调用时,this 指的时 window
在事件中,this 指向触发这个事件的对象
作为某个对象方法调用时,this 指向该对象。
在调用函数时使用 new,this 指向 new 出来的实例对象。
如果 apply、call 或 bind 方法
用于调用、创建一个函数,函数内的 this 就是作为参数传入这些方法的对象。
箭头函数和函数种类
三种函数
function add3(){} // 1.这种写法叫做函数声明
// 2-1.单独运行一个匿名函数,不符合语法要求,需要包裹
function (){ } // 2.这种是匿名函数
(function (){
// 由于没有执行该匿名函数,所以不会执行匿名函数体内的语句。
console.log("匿名函数");
})
// 2-2.如果需要执行匿名函数
(function (){
console.log("匿名函数");
})()
var add2 = function(){} // 3.这种写法叫做函数表达式
const add = (a, b) => a + b // 箭头(同样也是表达式)
匿名函数的 this
匿名函数(或普通函数)的 this 值取决于它是如何被调用的。在匿名函数内,this 通常会指向调用它的对象。
const obj = {
name: "John",
greet: function () {
console.log("Hello, " + this.name);
},
};
obj.greet(); // 输出 "Hello, John"
const greetFunction = obj.greet;
greetFunction(); // 输出 "Hello, undefined"
在第一个例子中,this 指向了 obj,因此输出是正确的。但在第二个例子中,greetFunction 被解绑了,this 指向全局对象(在浏览器中通常是 window),因此输出是 undefined。
this 题目
var length = 10;
function fn() {
console.log("1:", this, "-:", this.length);
}
var obj = {
length: 5,
callApi: function (fn) {
console.log("2:", this.length); // test: 5 这里匿名函数作为对象的属性被调用,所以this为obj
console.log("3:", arguments[0]);
fn(); // 这里等于fn()作为普通函数被调用,所以this为window,所以为10
// 这里arguments为类数组,它的属性名为下标,可以理解为是一个以数字
// 为属性名的对象,那么arguments[0]中的this就指向arguments,而arguments本身具有length属性,表示参数的个数
arguments[0]();
},
};
obj.callApi(fn, 3);
/*
2: 5
3: ƒ fn() {
console.log('1:', this, '-:', this.length)
}
1: Window {window: Window, self: Window, document: document, name: '', location: Location, …} -: 10
1: Arguments(2) [ƒ, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]0: ƒ fn()1: 3callee: ƒ (fn)length: 2Symbol(Symbol.iterator): ƒ values()[[Prototype]]: Object -: 2
*/