-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
About this #2
Comments
this提供了一种更优雅的方式隐式“传递”一个对象引用,因此可以将API设计得更加简介并易于复用。
this到底是什么this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。 当一个函数被调用时,会创建一个活动记录(有时候也成为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性,会在函数执行的过程中用到。
|
2.1 调用位置因为某些编程模式可能会隐藏真正的调用位置 最重要是要分析 调用栈(就是为了到达当前执行位置所调用的所有函数)。我们关心的调用位置就在当前正在执行的函数的前一个调用中。 看看什么是调用栈和调用位置: // 调用栈和调用位置
function baz() {
// 当前的调用栈是: baz
// 因此,当前调用位置的是 全局作用域
console.log('baz')
bar() // <-- bar的调用位置
}
function bar() {
// 当前的调用栈是baz -> bar
// 因为,当前调用位置在baz中
console.log('bar')
foo() // <-- foo的调用位置
}
function foo() {
// 当前的调用栈是 baz -> bar -> foo
// 因此,当前调用位置在bar中
console.log("foo")
}
baz() // <-- baz的调用位置 |
2.2 绑定规则来看看在函数的执行过程中调用位置如何决定this的绑定对象。 2.2.1 默认绑定介绍最常用的函数调用类型: 独立函数调用。可以把这条规则看做是无法应用其他规则时的默认规则。 function foo() {
console.log(this.a)
}
var a = 2
foo() // 2 我们可以看到当调用foo()时,this.a被解析成了全局变量a。为什么?因为在本例中,函数调用时应用了this的 默认绑定,因此this指向全局对象。
2.2.2 隐式绑定调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含,不过这种说法可能会造成一些误导。 function foo() {
console.log(this.a)
}
var obj = {
a:2,
foo: foo
}
obj.foo() // 2
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42 隐式丢失一个最常见的this绑定问题就是被 隐式绑定 的函数会丢失绑定对象,也就是说他会应用 默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否是严格模式。 function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性
bar(); // "oops, global"
一种更微妙、更常见并且更出乎意料的清空发生在传入回调函数时:function foo() {
console.log( this.a );
}
function doFoo(fn) {
// fn其实引用的是 foo
fn(); // <-- 调用位置!
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // a 是全局对象的属性
doFoo( obj.foo ); // "oops, global"
回调函数丢失this绑定是非常常见的。除此之外,还有一种情况this的行为会出乎我们意料:调用回调函数的函数可能会修改this。在一些流行的JavaScript库中事件处理器常会把回调函数的this强制绑定到触发事件的DOM元素上。 无论是哪种情况,this的改变都是意想不到的,实际上你无法控制回调函数的执行方式,因此就没有办法控制会影响绑定的调用位置。之后我们会介绍如何通过固定this来修复这个问题。 |
2.2.3 显示绑定像上一节看到的那样,在分析 _隐式绑定_时,我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this间接(隐式)绑定到这个对象上。 如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,该怎么做呢?
思考下面的代码: function foo() {
console.log( this.a );
}
var obj = {
a:2
};
foo.call( obj ); // 2 通过foo.call(...),我们可以在调用foo时强制把它的的this绑定到obj上。
可惜,显示绑定任然无法解决我们之前提出的 丢失绑定问题。 1. 硬绑定但是现实绑定的一个变种可以解决这个问题。 function foo() {
console.log(this.a)
}
var obj = {
a:2
}
var bar = function() {
foo.call(obj)
}
bar() //2
setTimeout(bar, 100) //2
// 硬绑定的bar不可能再修改它的this
// `bar` hard binds `foo`'s `this` to `obj`
// so that it cannot be overriden
bar.call(window) // 2 我们创建了函数bar(),并在它的内部手动调用了foo.call(obj),因此强制把foo的this绑定到了obj。无论之后如何调用函数bar,它总会手动在obj上调用foo。这种绑定是一种显示的强制绑定,因此我们称之为硬绑定hard binding
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a: 2
};
var bar = function() {
return foo.apply( obj, arguments );
};
var b = bar( 3 ); // 2 3
console.log( b ); // 5 Another way to express this pattern is to create a re-usable helper: function foo(something) {
console.log(this.a ,something)
return this.a + something
}
// 简单的辅助绑定函数
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 由于 硬绑定 是一种非常常用的模式,所以在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) // 5
2. API调用的“上下文”第三方库的许多函数,以及JavaScript语言和宿主环境中许多新的内置函数,都提供了一个可选的参数,通常被称为“上下文”(context),其作用和bind(..)一样,确保你的回调函数使用指定的this。 For instance: function foo(el) {
console.log( el, this.id );
}
var obj = {
id: "awesome"
};
// use `obj` as `this` for `foo(..)` calls
// 调用foo(..)时把this绑定到obj
[1, 2, 3].forEach( foo, obj ); // 1 awesome 2 awesome 3 awesome |
2.2.4 new绑定在讲解它之前我们首先需要澄清一个非常常见的关于JavaScript中函数和对象的误解。 something = new MyCalss(...) JavaScript也有一个new操作符,使用方法看起来也和那些面向类的语言一样,绝大多数开发者都认为JavaScript中的new的机制也和那些语言一样。然而,JavaScript中new的机制实际上和面向类的语言完全不同。 举例来说,思考一下Number(..)作为构造函数时的行为,ES5.1中这样描述它:
所以,包括内置对象函数在内的所有函数都可以用new来调用,这种函数调用被称为_构造函数调用_。这里有一个重要但非常细微的区别:实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。
思考以下的代码: function foo(a) {
this.a = a
}
var bar = new foo(2)
console.log(bar.a) // 2 使用new来调用foo(..)时,我们会构造一个新对象并把它绑定到foo(..)调用中的this上。new是最后一种可以影响函数调用时this绑定行为的方法,我们称之为new绑定。 |
2.3 优先级如果调用位置可以应用多条规则怎么办? 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
现在我们需要搞清楚 new 绑定和隐式绑定的优先级谁高谁低: function foo(something) {
this.a = something
}
var obj1 = {foo: foo}
var obj2 = {}
obj1.foo(2)
obj1.a // 2
obj1.foo.call(obj2, 3)
obj2.a // 3
var bar = new obj1.foo(4)
obj1.a // 2
bar.a // 4 可以看到 new 绑定比隐式绑定优先级高。但是 new 绑定和显式绑定谁的优先级更高呢?
For example: function foo(p1,p2) {
this.val = p1 + p2;
}
// 之所以使用 null 是因为在本例中我们并不关心硬绑定的 this 是什么
// 反正使用 new 时 this 会被修改
var bar = foo.bind( null, "p1" );
var baz = new bar( "p2" );
baz.val; // p1p2 判断this可以按照下面的顺序来进行判断:
var bar = new foo()
var bar = foo.call(obj2)
var bar = obj1.foo()
var bar = foo() 对于正常的函数调用来说,理解了这些知识你就可以明白this的绑定原理了。 |
2.4 绑定例外你认为应当应用其他绑定规则时,实际上应用的可能是默认绑定规则。 2.4.1 被忽略的this如果你把 function foo() {
console.log(this.a)
}
var a = 2
foo.call(null) //2 那什么情况下会传入null呢? foo.apply(null, [2, 3]) // a:2, b:3
//使用bind(..)进行柯里化
var bar = foo.bind(null, 2)
bar(3) // a:2, b:3 这两种方法都需要传入一个参数当作this的绑定对象。如果函数并不关心this的话,你仍然需要传入一个占位值,这时null是一个不错的选择。
然而,总是使用 null 来忽略 this 绑定可能产生一些副作用。如果某个函数确实使用了 更安全的this一种“更安全”的做法是传入一个特殊的对象,把 this 绑定到这个对象不会对你的程序 如果我们在忽略 this 绑定时总是传入一个 DMZ 对象,那就什么都不用担心了,因为任何 在 JavaScript 中创建一个空对象最简单的方法都是 Object.create(null) function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// 我们的 DMZ 空对象
var ø = Object.create( null );
// 把数组展开成参数
foo.apply( ø, [2, 3] ); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar = foo.bind( ø, 2 );
bar( 3 ); // a:2, b:3 使用变量名 ø 不仅让函数变得更加“安全”,而且可以提高代码的可读性,因为 ø 表示 2.4.2 间接引用另一个需要注意的是,你有可能(有意或无意地)创建了一个函数的“简介引用”,在这种情况下,调用这个函数会应用默认绑定规则。 function 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
2.4.3 软绑定硬绑定可以把this强制绑定到指定对象(除了使用new时),防止函数调用应用默认绑定规则。问题在于,硬绑定会大大降低函数的灵活性,使用硬绑定之后就无法使用隐式绑定或者显式绑定来修改 this。 It would be nice if there was a way to provide a different default for default binding (not 可以通过一种被称为软绑定的方法来实现我们想要的效果: if (!Function.prototype.softBind) {
Function.prototype.softBind = function (obj) {
var fn = this,
curried = [].slice.call(arguments, 1),
bound = function bound() {
return fn.apply(
(!this ||
(typeof window !== "undefined" &&
this === window) ||
(typeof global !== "undefined" &&
this === global)
) ? obj : this,
curried.concat.apply(curried, arguments)
);
};
bound.prototype = Object.create(fn.prototype);
return bound;
};
} 除了软绑定之外,softBind(..) 的其他原理和 ES5 内置的 bind(..) 类似。它会对指定的函 下面我们看看 softBind 是否实现了软绑定功能: function foo() {
console.log("name: " + this.name);
}
var obj = { name: "obj" },
obj2 = { name: "obj2" },
obj3 = { name: "obj3" };
var fooOBJ = foo.softBind( obj );
fooOBJ(); // name: obj
obj2.foo = foo.softBind(obj);
obj2.foo(); // name: obj2 <---- 看!!!
fooOBJ.call( obj3 ); // name: obj3 <---- 看!
setTimeout( obj2.foo, 10 );
// name: obj <---- 应用了软绑定 可以看到,软绑定版本的 foo() 可以手动将 this 绑定到 obj2 或者 obj3 上,但如果应用默 |
2.5 this词法我们之前介绍的四条规则已经可以包含所有正常的函数。但是ES6中介绍了一种无法使用这些规则的特殊函数类型:箭头函数。 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! foo()内部创建的箭头函数会捕获调用时foo()的this。由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。(new 也不行!) function foo() {
setTimeout(() => {
// 这里的 this 在此法上继承自 foo()
console.log( this.a );
},100);
}
var obj = {
a:2
};
foo.call( obj ); // 2 箭头函数可以像 bind(..) 一样确保函数的 this 被绑定到指定对象,此外,其重要性还体现在它用更常见的词法作用域取代了传统的 this 机制。实际上,在 ES6 之前我们就已经在使用一种几乎和箭头函数完全一样的模式。 function foo() {
var self = this; // lexical capture of this
setTimeout( function(){
console.log( self.a );
}, 100 );
}
var obj = {
a: 2
};
foo.call( obj ); // 2 虽然 self = this 和箭头函数看起来都可以取代 bind(..),但是从本质上来说,它们想替 2.6 小结如果要判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面四条规则来判断this的绑定对象。
一定要注意,有些调用可能在无意中使用默认绑定规则。如果想“更安全”地忽略 this 绑 |
exercise 1
尚未理解的内容:
The text was updated successfully, but these errors were encountered: