date: 2019-07-23 
tags: js  
最近开始做面试的复习,打算顺便把一些看过好多遍的js知识总结提炼一下。这次从this开始。本文的内容来自于[You Don't Know JS: this & Object Prototypes]([https://github.com/getify/You-Dont-Know-JS/tree/master/this%20%26%20object%20prototypes](https://github.com/getify/You-Dont-Know-JS/tree/master/this %26 object prototypes))。非常好的一本书,非常推荐。
this可以说是js中非常令人困惑的一个关键词了。首先看一个this的用例:
function identify() {
	return this.name.toUpperCase();
}
function speak() {
	var greeting = "Hello, I'm " + identify.call( this );
	console.log( greeting );
}
var me = {
	name: "Kyle"
};
var you = {
	name: "Reader"
};
identify.call( me ); // KYLE
identify.call( you ); // READER
speak.call( me ); // Hello, I'm KYLE
speak.call( you ); // Hello, I'm READER相较于传递参数,使用this似乎更加优雅。
在说this是什么之前,首先来说一下this不是什么:
this并不指向函数自己
function foo(num) {
	console.log( "foo: " + num );
	// keep track of how many times `foo` is called
	this.count++;
}
foo.count = 0;
var i;
for (i=0; i<10; i++) {
	if (i > 5) {
		foo( i );
	}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// how many times was `foo` called?
console.log( foo.count ); // 0 -- WTF?如果函数要调用自己,或者给自己加(调整)某些属性,需要用命名函数,然后用函数名来控制。
function foo() {
	foo.count = 4; // `foo` refers to itself
}
setTimeout( function(){
	// anonymous function (no name), cannot
	// refer to itself
}, 10 );注意,有一种已被废弃的arguments.callee也可以指向函数。
如果非要使用this的话,上面的例子可以改为:
function foo(num) {
	console.log( "foo: " + num );
	// keep track of how many times `foo` is called
	// Note: `this` IS actually `foo` now, based on
	// how `foo` is called (see below)
	this.count++;
}
foo.count = 0;
var i;
for (i=0; i<10; i++) {
	if (i > 5) {
		// using `call(..)`, we ensure the `this`
		// points at the function object (`foo`) itself
		foo.call( foo, i );
	}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// how many times was `foo` called?
console.log( foo.count ); // 4关于this,更容易混淆的是它指代于this的scope。这个说法有些棘手,因为一方面它有点道理,但是另一方面,它相当容易误导人。
需要注意的是,this并不指向函数的lexical scope。虽然scope的确是每个对象的特性,但是javascript代码无法访问它,所以this也不可能使用它。
function foo() {
	var a = 2;
	this.bar();
}
function bar() {
	console.log( this.a );
}
foo(); //undefined上面这段代码里面,foo阴差阳错得调用了bar,但是bar没法初级foo.a。
实际上,this指向的是函数被调用时的调用栈(call-site)。所以我们先来理解一下call-site。简单来说,call-site就是指函数被调用的地方。但是具体识别起来,可能有点复杂。来看下面的例子。
function baz() {
    // call-stack is: `baz`
    // so, our call-site is in the global scope
    console.log( "baz" );
    bar(); // <-- call-site for `bar`
}
function bar() {
    // call-stack is: `baz` -> `bar`
    // so, our call-site is in `baz`
    console.log( "bar" );
    foo(); // <-- call-site for `foo`
}
function foo() {
    // call-stack is: `baz` -> `bar` -> `foo`
    // so, our call-site is in `bar`
    console.log( "foo" );
}
baz(); // <-- call-site for `baz`这个例子很清晰的描述了简单的调用栈。
那么简单介绍完call-site,让我们把注意力转回this。this实际上有以下4个规则。
new Binding第一个是最常见的,也就是standalone function invocation。也是其他规则不适用的时候需要使用的规则。
function foo() {
	console.log( this.a );
}
var a = 2;
foo(); // 2这里default binding把foo中的this绑定到了其call-site上,所以调用了global object a。不过需要注意的是,在"use strict"的时候,global object是不能参与default binding的。
function foo() {
	"use strict";
	console.log( this.a );
}
var a = 2;
foo(); // TypeError: `this` is `undefined`注意,上面只是个例子,在实际应用中不要混合strict和non-strict。
第二条规则是,call-site是否有context object (owning or containing object)。
function foo() {
	console.log( this.a );
}
var obj = {
	a: 2,
	foo: foo
};
obj.foo(); // 2注意,无论foo是在定义obj的时候就添加了,还是之后赋值的,都不能认为这个函数都是被obj contained or owned。而是在调用函数的时候,foo是通过obj调用的,
注意,只有最后一层object reference是重要的。
function foo() {
	console.log( this.a );
}
var obj2 = {
	a: 42,
	foo: foo
};
var obj1 = {
	a: 2,
	obj2: obj2
};
obj1.obj2.foo(); // 42非常需要注意的是想如下的例子:
function foo() {
	console.log( this.a );
}
var obj = {
	a: 2,
	foo: foo
};
var bar = obj.foo; // function reference/alias!
var a = "oops, global"; // `a` also property on global object
bar(); // "oops, global"在这个例子中,虽然bar看起来像是obj.foo的引用,但实际上只是foo的又一份引用而已。所以调用bar会回到default binding。
更恶心的是我们使用回调函数的时候:
function foo() {
	console.log( this.a );
}
function doFoo(fn) {
	// `fn` is just another reference to `foo`
	fn(); // <-- call-site!
}
var obj = {
	a: 2,
	foo: foo
};
var a = "oops, global"; // `a` also property on global object
doFoo( obj.foo ); // "oops, global"注意传递参数实际上就是一次赋值,明白这个就更容易明白上面的例子了。
系统自带的函数也是一样:
function foo() {
	console.log( this.a );
}
var obj = {
	a: 2,
	foo: foo
};
var a = "oops, global"; // `a` also property on global object
setTimeout( obj.foo, 100 ); // "oops, global"在implicit binding中,我们可以通过在对象中加入函数的引用,并通过这个对象来调用函数来bind这个函数。那么,如果我们需要强制某个函数来调用某个的对象呢?
所有的函数的prototype都有call(...)和bind(...)这两个方法。这两个函数都会以一个对象作为其第一个参数,并以这个对象作为this。这两种,我们称之为explicit binding。
function foo() {
	console.log( this.a );
}
var obj = {
	a: 2
};
foo.call( obj ); // 2注意,如果传入的对象是基本类型,会被装箱。以及apply和call是非常相似的,只是后面的参数不同。
function foo() {
	console.log( this.a );
}
var obj = {
	a: 2
};
var bar = function() {
	foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// `bar` hard binds `foo`'s `this` to `obj`
// so that it cannot be overriden
bar.call( window ); // 2通过上面这个trick就可以进行hard binding了。
function foo(something) {
	console.log( this.a, something );
	return this.a + something;
}
// simple `bind` helper
function bind(fn, obj) {
	return function() {
		return fn.apply( obj, arguments );
	};
}
var obj = {
	a: 2
};
var bar = bind( foo, obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5一个可重用的简单的helper。
因为hard binding太常用了,所以ES5中加入了Function.prototype.bind
function foo(something) {
	console.log( this.a, something );
	return this.a + something;
}
var obj = {
	a: 2
};
var bar = foo.bind( obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5new Binding最后一个规则需要我们去重新思考一下一个经常错误理解的概念。
somthing = new MyClass(...);javascript中的new和其他的class-oriented语言很不同。
首先,在js中,constructor就是一个普通的函数,任何一个函数前面用new来进行调用就是constructor。当一个函数被new invoked的时候,会自动进行这样几个事:
[[Prototype]]-linkedthisnew-invoked函数会直接返回这个凭空创建的对象。如:
function foo(a) {
	this.a = a;
}
var bar = new foo( 2 );
console.log( bar.a ); // 2我们称这种bind叫new binding。
首先,default binding是优先级最低的。
function foo() {
	console.log( this.a );
}
var obj1 = {
	a: 2,
	foo: foo
};
var obj2 = {
	a: 3,
	foo: foo
};
obj1.foo(); // 2
obj2.foo(); // 3
obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2上面例子看出explicit binding优先于implicit。
function foo(something) {
	this.a = something;
}
var obj1 = {
	foo: foo
};
var obj2 = {};
obj1.foo( 2 );
console.log( obj1.a ); // 2
obj1.foo.call( obj2, 3 );
console.log( obj2.a ); // 3
var bar = new obj1.foo( 4 );
console.log( obj1.a ); // 2
console.log( bar.a ); // 4这个例子看出new binding优先于implicit binding。
因为不能同时使用new和call/apply,所以不能进行new foo.call(obj1),所以我们会用bind进行测试:
function foo(something) {
	this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 );
console.log( obj1.a ); // 2
var baz = new bar( 3 );
console.log( obj1.a ); // 2
console.log( baz.a ); // 3注意,这里bind的表现和我们上面自己写的bind是不同的。只需要记住new binding可以覆盖hard binding就好。
所以我们得到的顺序是:
new 调用,如果有this就是这个返回的函数。call或apply调用,或者藏于一个bind中,如果是,那么this指这个对象。this就是这个context对象"use strict",如果是,那么为undefined,不然就是global对象。有一些例外。
this如果向call/apply/bind传入的是null或是undefined,bind的效果会被忽略。
function foo() {
	console.log( this.a );
}
var a = 2;
foo.call( null ); // 2this可以用一个特殊的global变量来处理this
function foo(a,b) {
	console.log( "a:" + a + ", b:" + b );
}
// our DMZ empty object
var ø = Object.create( null );
// spreading out array as parameters
foo.apply( ø, [2, 3] ); // a:2, b:3
// currying with `bind(..)`
var bar = foo.bind( ø, 2 );
bar( 3 ); // a:2, b:3function foo() {
	console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
(p.foo = o.foo)(); // 2某些操作会返回的是函数的引用,所以可能会触及default binding。
这里书给提供了一种soft binding,有兴趣的可以自行查阅。
thisES6引入的箭头函数会固定绑定其被声明的位置的this。如:
function foo() {
	// return an arrow function
	return (a) => {
		// `this` here is lexically adopted from `foo()`
		console.log( this.a );
	};
}
var obj1 = {
	a: 2
};
var obj2 = {
	a: 3
};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, not 3!很常用的一个场景是:
function foo() {
	setTimeout(() => {
		// `this` here is lexically adopted from `foo()`
		console.log( this.a );
	},100);
}
var obj = {
	a: 2
};
foo.call( obj ); // 2实际上,上面的例子等同于:
function foo() {
	var self = this; // lexical capture of `this`
	setTimeout( function(){
		console.log( self.a );
	}, 100 );
}
var obj = {
	a: 2
};
foo.call( obj ); // 2