Skip to main content

this-箭头函数

this-->执行上下文所属的作用域

JavaScript 中 this 指的是执行上下文所属的作用域。所以函数中的 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。

  1. 箭头函数表达式的语法比函数表达式更简洁,并且没有自己的 this,arguments,super 或 new.target。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。

    没有 prototype 属性,没办法连接它的实例的原型链,所以箭头函数就无法作为构造函数。

  2. 箭头函数不创建上下文,因此箭头函数的内部 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 出来的实例对象。

new 做了什么?原型-原型链

如果 apply、call 或 bind 方法

用于调用、创建一个函数,函数内的 this 就是作为参数传入这些方法的对象。

参考:bind-call-apply

箭头函数和函数种类

三种函数

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
*/