关于this作用域的一些记录
一、全局的this(浏览器)
console.log(this.document === document) //true
console.log(this === window) //true
this.a = 37;
console.log(window.a); //37
二、一般函数的this(浏览器)
function f1(){
return this;
}
f1()===window; //true,global object
三、作为对象方法的函数的this
var o = {
prop:37,
f:function(){
return this.prop;
}
};
console.log(o.f()); //37
f作为一个对象的方法,那么作为对象的方法去调用的时候,比如o.f调用的时候,这种情况,这个this,一般会指向对象
var o = {
prop:37
};
function indepedent(){
return this.prop;
}
o.f = indepedent;
console.log(o.f()); //37
这里并不是去看函数是再怎么样创建的,而是只要将这个函数作为对象的方法,这个o.f去调用的话,那么这个this就会指向这个o
关于this,一般来说,谁调用了方法,该方法的this就指向谁,如:
function foo(){
console.log(this.a)
}
var a = 3;
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 输出2,因为是obj调用的foo,所以foo的this指向了obj,而obj.a = 2
如果存在多次调用,对象属性引用链只有上一层或者说最后一层在调用位置中起作用,如:
function foo() {
console.log( this.a )
}
var obj2 = {
a: 42,
foo: foo
}
var obj1 = {
a: 2,
obj2: obj2
}
obj1.obj2.foo(); // 42
四、对象原型链上的this
var o = {
f:function(){
return this.a + this.b;
}
}
var p = Object.create(o);
p.a = 2;
p.b = 6;
console.log(p.f()); //8
这里p的原型是o,那么p.f的时候调用的是对象o上面的函数属性f,this也指向p
五、get/set方法与this
function modules(){
return Math.sqrt(this.re * this.re + this.im * this.im);
}
var o = {
re :1,
im:-1,
get phase(){
return this.im+this.re;
}
};
Object.defineProperty(o,'modules',{
get:modules,enumerable:true,configurable:true
});
console.log(o.phase,o.modules); // 0 根号2
六、构造器中的this
function MyClass(){
this.a = 37;
}
var o = new MyClass();
console.log(o.a); //37
function C2(){
this.a = 37;
return{
a:38
}
}
o=new C2();
console.log(o.a); //38
new一个MyClass空对象,如果没有返回值,默认会返回this,有返回值,那么o就指向返回的对象了
在传统面向类的语言中,使用new初始化类的时候会调用类中的构造函数,但是JS中new的机制实际上和面向类和语言完全不同。
使用new
来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:
- 创建(或者说构造)一个全新的对象
- 这个新对象会被执行[[Prototype]]连接
- 这个新对象会绑定到函数调用的this
- 如果函数没有返回其他对象,那么new表达式中的函数会自动返回这个新对象 如:
function foo(a){
this.a = a
}
var bar = new foo(2);
console.log(bar.a); // 2
使用new来调用foo(…)时,我们会构造一个新对象并把它绑定到foo(…)调用中的this上。new是最后一种可以影响函数调用时this绑定行为的方法,我们称之为new绑定。
七、bind方法与this
function f(){
return this.a;
}
var g = f.bind({
a:'test'
});
console.log(g()); //test
var o = {
a:37,
f:f,
g:g
};
console.log(o.f(),o.g()); //37,test
八、call/apply方法与this
function add(c,d){
return this.a + this.b+c+d;
}
var o = {
a:1,
b:3
};
add.call(o,5,7); //1+3+5+7 = 16 add(5,7)
add.apply(o,[10,20]); //1+3+10+20 = 34
function bar(){
console.log(Object.prototype.toString.call(this));
}
bar.call(7); //[Objedt Number]
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
这里简单做一下解释: 在bar函数中,foo使用apply函数绑定了obj,也就是说foo中的this将指向obj,与此同时,使用arguments(不限制传入参数的数量)作为参数传入foo函数中;所以在运行bar(3)的时候,首先输出obj.a也就是2和传入的3,然后foo返回了两者的相加值,所以b的值为5
同样,本例也可以使用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
九、隐式丢失
一个最常见的this绑定问题就是被隐式绑定
的函数会丢失绑定对象,也就是说他回应用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否是严格模式。
function foo() {
console.log( this.a )
}
var obj1 = {
a: 2,
foo: foo
}
var bar = obj1.foo; // 函数别名!
var a = "oops, global"; // a是全局对象的属性
bar(); // "oops, global"
虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定
一个更微妙、更常见并且更出乎意料的情况发生在传入回调函数时
:
function foo() {
console.log( this.a )
}
function doFoo( fn ){
// fn 其实引用的是 foo
fn(); //
参数传递其实就是一种隐式赋值
,因此我们传入函数时也会被隐式赋值,所以结果和上一个例子一样,如果把函数传入语言内置的函数而不是传入自己声明的函数(如setTimeout等),结果也是一样的
十、this的优先级
毫无疑问,默认绑定的优先级是四条规则中最低的,所以我们可以先不考虑它。
隐式绑定和显式绑定哪个优先级更高?我们来测试一下:
function foo(a){
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);
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绑定
比隐式绑定
优先级高。但是new绑定
和显式绑定
谁的优先级更高呢?
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
可以看到,new绑定
修改了硬绑定
中的this,所以new绑定
的优先级比显式绑定
更高。
之所以要在new中使用硬绑定函数,主要目的是预先设置函数的一些参数,这样在使用new进行初始化时就可以只传入其余的参数。bind(…)的功能之一就是可以把除了第一个参数(第一个参数用于绑定this)之外的其他参数都传给下层的函数(这种技术称为“部分应用”,是“柯里化
”的一种)。举例来说:
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在箭头函数中的应用
箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。
我们来看一下箭头函数的词法作用域:
function foo() {
// 返回一个箭头函数
return (a) => {
// this继承自foo()
console.log(this.a)
};
}
var obj1 = {
a: 2
};
var obj2 = {
a: 3
};
var bar = foo.call(obj1);
bar.call(obj2); // 2, 不是3!
foo()内部创建的箭头函数会捕获调用时foo()的this。由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。(new也不行!)
总结:
如果要判断一个运行中的函数的this绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断this的绑定对象。
- 由new调用?绑定到新创建的对象。
- 由call或者apply(或者bind)调用?绑定到指定的对象。
- 由上下文对象调用?绑定到那个上下文对象。
- 默认:在严格模式下绑定到undefined,否则绑定到全局对象。
规则0:函数本身是一个特殊类型,大多数时候,可以认为是一个变量。
1 2 3 4 5 6 7 8 9 10 |
|
都可以认为是创建了一个变量,这个变量的值就是一个函数。
规则1:如果一个函数,是某个对象的key 值,那么,this就指向这个对象。
这个规则很好理解:
1 2 3 4 5 6 7 8 |
|
函数就是一个变量,但是可以绑定到某个对象的下面,并且 this 就会指向 o 对象。
这里必须要注意,没有被绑定的对象,默认this 指向window 对象。
举几个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
还必须注意的是,绑定没有传递性,比如上面的嵌套的函数,a绑定到 o 对象,那么就影响了a函数,
而b 还是指向到window。
规则2:如果函数new 了一下,那么就会创建一个对象,并且this 指向 新创建的对象。
var o = new a();
这个时候,o 不再是个函数,而实际上,可以认为是这样的一个过程。
创建一个对象 var o = {};
然后,把this 指向 o,通过this 把 o 给初始化了。
规则3:通过apply 可以改变this 的指向
这个apply 的绑定就更加的灵活了,实际上,apply的功能和下面的功能差不多。
1 2 3 4 5 6 |
|
简单的,可以a.apply(obj, [obj]); // true
javascript 的this 可以简单的认为是 后期绑定,没有地方绑定的时候,默认绑定window。
综合实例:
jquery 里面有一个很常用的函数 each,可以把循环的对象元素绑定到this,方便操作。
这里只是简单的做个演示:
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
es6中箭头函数与普通函数的区别:
使用注意点
箭头函数有几个使用注意点。
(1)函数体内的this
对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new
命令,否则会抛出一个错误。
(3)不可以使用arguments
对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield
命令,因此箭头函数不能用作 Generator 函数。
上面四点中,第一点尤其值得注意。this
对象的指向是可变的,但是在箭头函数中,它是固定的。
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 });
// id: 42
上面代码中,setTimeout
的参数是一个箭头函数,这个箭头函数的定义生效是在foo
函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时this
应该指向全局对象window
,这时应该输出21
。但是,箭头函数导致this
总是指向函数定义生效时所在的对象(本例是{id: 42}
),所以输出的是42
。
箭头函数可以让setTimeout
里面的this
,绑定定义时所在的作用域,而不是指向运行时所在的作用域。下面是另一个例子。
function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭头函数
setInterval(() => this.s1++, 1000);
// 普通函数
setInterval(function () {
this.s2++;
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0
上面代码中,Timer
函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this
绑定定义时所在的作用域(即Timer
函数),后者的this
指向运行时所在的作用域(即全局对象)。所以,3100 毫秒之后,timer.s1
被更新了 3 次,而timer.s2
一次都没更新。
箭头函数可以让this
指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM 事件的回调函数封装在一个对象里面。
var handler = {
id: '123456',
init: function() {
document.addEventListener('click',
event => this.doSomething(event.type), false);
},
doSomething: function(type) {
console.log('Handling ' + type + ' for ' + this.id);
}
};
上面代码的init
方法中,使用了箭头函数,这导致这个箭头函数里面的this
,总是指向handler
对象。否则,回调函数运行时,this.doSomething
这一行会报错,因为此时this
指向document
对象。
this
指向的固定化,并不是因为箭头函数内部有绑定this
的机制,实际原因是箭头函数根本没有自己的this
,导致内部的this
就是外层代码块的this
。正是因为它没有this
,所以也就不能用作构造函数。
所以,箭头函数转成 ES5 的代码如下。
// ES6
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
// ES5
function foo() {
var _this = this;
setTimeout(function () {
console.log('id:', _this.id);
}, 100);
}
上面代码中,转换后的 ES5 版本清楚地说明了,箭头函数里面根本没有自己的this
,而是引用外层的this
。
请问下面的代码之中有几个this
?
function foo() {
return () => {
return () => {
return () => {
console.log('id:', this.id);
};
};
};
}
var f = foo.call({id: 1});
var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1
上面代码之中,只有一个this
,就是函数foo
的this
,所以t1
、t2
、t3
都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的this
,它们的this
其实都是最外层foo
函数的this
。
除了this
,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments
、super
、new.target
。
function foo() {
setTimeout(() => {
console.log('args:', arguments);
}, 100);
}
foo(2, 4, 6, 8)
// args: [2, 4, 6, 8]
上面代码中,箭头函数内部的变量arguments
,其实是函数foo
的arguments
变量。
另外,由于箭头函数没有自己的this
,所以当然也就不能用call()
、apply()
、bind()
这些方法去改变this
的指向。
(function() {
return [
(() => this.x).bind({ x: 'inner' })()
];
}).call({ x: 'outer' });
// ['outer']
上面代码中,箭头函数没有自己的this
,所以bind
方法无效,内部的this
指向外部的this
。
长期以来,JavaScript 语言的this
对象一直是一个令人头痛的问题,在对象方法中使用this
,必须非常小心。箭头函数”绑定”this
,很大程度上解决了这个困扰。
在react中跟this相关的注意:
你必须谨慎对待 JSX 回调函数中的 this
,类的方法默认是不会绑定 this
的。如果你忘记绑定 this.handleClick
并把它传入 onClick
, 当你调用这个函数的时候 this
的值会是 undefined
。
这并不是 React 的特殊行为;它是函数如何在 JavaScript 中运行的一部分。通常情况下,如果你没有在方法后面添加 ()
,例如 onClick={this.handleClick}
,你应该为这个方法绑定 this
。
如果使用 bind
让你很烦,这里有两种方式可以解决。如果你正在使用实验性的属性初始化器语法,你可以使用属性初始化器来正确的绑定回调函数:
class LoggingButton extends React.Component {
// This syntax ensures `this` is bound within handleClick.
// Warning: this is *experimental* syntax.
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
这个语法在 Create React App 中默认开启。
如果你没有使用属性初始化器语法,你可以在回调函数中使用 箭头函数:
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// This syntax ensures `this` is bound within handleClick
return (
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
);
}
}
使用这个语法有个问题就是每次 LoggingButton
渲染的时候都会创建一个不同的回调函数。在大多数情况下,这没有问题。然而如果这个回调函数作为一个属性值传入低阶组件,这些组件可能会进行额外的重新渲染。我们通常建议在构造函数中绑定或使用属性初始化器语法来避免这类性能问题。
向事件处理程序传递参数
通常我们会为事件处理程序传递额外的参数。例如,若是 id
是你要删除那一行的 id,以下两种方式都可以向事件处理程序传递参数:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
上述两种方式是等价的,分别通过 arrow functions 和 Function.prototype.bind
来为事件处理函数传递参数。
上面两个例子中,参数 e
作为 React 事件对象将会被作为第二个参数进行传递。通过箭头函数的方式,事件对象必须显式的进行传递,但是通过 bind
的方式,事件对象以及更多的参数将会被隐式的进行传递。
值得注意的是,通过 bind
方式向监听函数传参,在类组件中定义的监听函数,事件对象 e
要排在所传递参数的后面,例如:
class Popper extends React.Component{
constructor(){
super();
this.state = {name:'Hello world!'};
}
preventPop(name, e){ //事件对象e要放在最后
e.preventDefault();
alert(name);
}
render(){
return (
<div>
<p>hello</p>
{/* Pass params via bind() method. */}
<a href="https://reactjs.org" onClick={this.preventPop.bind(this,this.state.name)}>Click</a>
</div>
);
}
}
深入了解:
https://github.com/mqyqingfeng/Blog/issues/7