fe ? not exsit ?

走进基础:this了解一下

this of javascript

  • 三大特点

    • this令人困惑。

    • this运行时绑定的产物

    • 依赖函数调用的上下文环境。

javascript的作者当时戏说他也弄不清,就记住那四种方式(下文抛出)就ok。知识点1,一个函数调用会有一个执行环境。这个执行环境包含了是如何被调用的、传递的参数信息、this也是其中的一个属性。知识点2,多次提到的上下文,看一下它的生命周期。

周期图;

大多数人的误区错把this和词法作用域混淆,上图也可以看出创建阶段计算出this和作用域链。那他们有关系吗?明确一下javascript中的this不会以任何方式指向词法作用域。作用域好像是一个将所有可用标识符作为属性的对象,这从内部来说是对的。但是JavasScript代码不能访问作用域“对象”。它是引擎的内部实现。

meaning of exsit

引入一个函数传参的例子,感受一下通过一个context传递调用的对象与this隐式传递的对比。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// use param context 

function animal(context) {
return context.name;
}

function bigDog(context) {
console.log( `hello,${animal(context)}`);
}

var name = 'anni';

bigDog(window); //hello anni

// use this

function animal() {
return this.name;
}

function bigDog() {
console.log(`hello,${animal()}`)
}

var name = 'anni';

bigDog(); //hello anni
  • 更加优雅的传递引用
  • 更加干净的API设计和更容易的复用

辨认规则:

前人经过长期使用总结出一套使用规律。this与它所在方法的调用点有关,与方法所在声明没有关系,看下文这个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// obj声明位置
var obj = {
name: 'i am obj',
test: function() {
console.log(this.name);
}
}
// obj1声明位置
var obj1 = {
name: 'i am obj1',
test1: obj.test
}
// obj1调用位置
obj1.test1(); // i am obj1

wow, 厉害,我能辨认this了,不!还差得远呢。接着看

1
2
3
4
5
6
7
8
9
10
11
12
13
// egg.1
function sayName() {
console.log(this.name);
}
var name = 'i am window';
sayName(); // i am window

//egg.2
var obj = {
name: 'i am obj',
say: sayName
}
setTimeout(obj.say, 1000); // i am window

what is the wrong? what is the fuck ?

然后又有一群人经过实验规律总结: this与它所在方法的调用点有关这条理论完全正确,不过这仅仅是规则,还需要配合方法论使用。确认where is this?前提是这条理论,还要配合以下四条方法论。

  1. 默认绑定,函数独立调用
1
2
3
4
5
6
// egg.1
function sayName() {
console.log(this.name);
}
var name = 'i am window';
sayName(); // i am window

egg.1 就是默认绑定的具体应用。首先调用点是函数直接调用的。那么为什么默认绑定使用于这里?因为没有其他规则适用了。No way, 无法反驳,游戏的规则就是这样。

  1. 隐含绑定,如若隐含丢失则退回默认绑定, 调用点一般是一个拥有者或者容器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// egg.2
// obj声明位置
var obj = {
name: 'i am obj',
test: function() {
console.log(this.name);
}
}
// obj1声明位置
var obj1 = {
name: 'i am obj1',
test1: obj.test
}
// obj1调用位置
obj1.test1(); // i am obj1

egg.2就是隐含绑定的具体应用。调用点是obj1,test的调用位置点指向obj1的引用,这个时候隐含绑定规则就会说这个对象的函数调用应该被this绑定。

隐含丢失,即隐含绑定失效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//egg.3
var animal1 = {
name: 'litchi',
say: function() {
console.log(this.name);
}
}
var animal2 = {
name: 'hyc',
say: function() {
var test = animal1.say;
test();
}
}
var name = 'hello';

animal2.say(); // hello

// egg.4 setTimeout setInterval 匿名函数执行的当前对象
var animal1 = {
name: 'litchi',
say: function() {
console.log(this.name);
}
}
var name = 'hyc';
setTimeout( animal1.say, 1000);

egg.3与egg.4 就是典型的隐含绑定中this丢失的实例。

  1. 明确绑定,call、apply
1
2
3
4
5
6
7
8
9
10
// egg.5
function foo() {
console.log( this.name );
}

var obj = {
name: 'litchi'
};

foo.call( obj ); // litchi

call应用,即明确绑定,apply和call没有除调用参数外无任何区别。但是它不能解决this绑定丢失、函数覆盖等等问题。

硬绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function sayName() {
console.log(this.name);
}

var name = 'hyc';

var obj = {
name: 'litchi'
}

function say() {
return sayName.call(obj);
}

say();

say.call(obj);

setTimeout(say, 1000);

//litchi
//litchi
//litchi

明确绑定的小变种, 通过一个函数包装的形式将this强制绑定到obj上。无论怎样都不会被覆盖。硬绑定 是一个如此常用的模式,它已作为 ES5 的内建工具提供:Function.prototype.bind。

1
2
3
4
5
6
7
8
9
10
11
12
13
function sayName() {
console.log(this.name);
}

var name = 'hyc';

var obj = {
name: 'litchi'
}
var a = sayName.bind(obj);

a(); //litchi
setTimeout(a, 1000); //litchi

自己封装一个bind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function bindObj( fn, obj) {
return function () {
return fn.call(obj);
}
}
function sayName() {
console.log(this.name);
}

var name = 'hyc';

var obj = {
name: 'litchi'
}
var a = bindObj(sayName, obj);

a(); //litchi
setTimeout(a, 1000); //litchi

接收我们回调的函数故意改变调用的 this。那些很流行的 JavaScript 库(jquery等)中的事件处理器就十分喜欢强制你的回调的 this 指向触发事件的 DOM 元素。

  1. new绑定,使用新构建的对象

首先,让我们重新定义 JavaScript 的“构造器”是什么。在 JS 中,构造器仅仅是一个函数,它们偶然地与前置的new操作符一起调用。它们不依附于类,它们也不初始化一个类。它们甚至不是一种特殊的函数类型。它们本质上只是一般的函数,在被使用 new 来调用时改变了行为。new的默认行为如下:

1. 新对象被构建分配内存
2. 将新创建的对象接入原型链
3. 将新创建的对象设置函数调用的this绑定
4. 除非函数返回一个它自己的其他对象,否则这个被new调用的函数将自动返回这个新构建的对象。
1
2
3
4
5
function foo(name) {
this.name = name || '';
}
var bar = new foo('litchi');
console.log(bar.name); //litchi

构建了一个新对象bar, 把bar作为函数调用的this。

es6中的箭头函数

this是根据函数声明的时候确定的,也就是跟随词法作用域。

node中的this

node cli 与浏览器环境保持一致,脚本环境不一致,不做讨论。