彻底深刻理解js之this
http://0313.name/archives/77#more-77
1.this的指向是在函数被调用的时候确定的;
2.在函数执行过程中不可修改this的指向;
var a = 10;
var obj = {
a: 20
}
function fn () {
this = obj; // 这句话试图修改this,运行后会报错
console.log(this.a);
}
fn();
两种情况的this指向
1.全局对象中的this
全局环境中的this指向它本身
// 通过this绑定到全局对象
this.a2 = 20;
// 通过声明绑定到变量对象,但在全局环境中,变量对象就是它自身
var a1 = 10;
// 仅仅只有赋值操作,标识符会隐式绑定到全局对象
a3 = 30;
// 输出结果会全部符合预期
console.log(a1);
console.log(a2);
console.log(a3);
2.函数中的this
结论:在一个函数上下文中,this由调用者提供,由调用函数的方式来决定,
如果调用者函数在一个对象中,被某个对象所拥有,那么该函数在调用时,内部的this指向该对象;
但若函数独立调用,在严格模式下use strict,该this指向undefined;非严格模式下自动指向全局对象;
//demo1
var a=20;
function fn(){
console.log(this.a)
}
fn();//20 调用者函数独立调用,非严格模式下指向全局对象,所以打印为20 ;严格模式下为undefined;
//demo2
var a=20;
function fn(){
function foo(){
console.log(this.a)
}
foo();
}
fn() //20 调用者函数独立调用,非严格模式下指向全局对象,所以打印为20 ;严格模式下为undefined;
//demo3
var a=20;
var obj={
a:10,
c:this.a+20,
fn:function(){
return this.a;
}
}
console.log(obj.c); //40
console.log(obj.fn()) //10
这个地方需要注意,当调用者不是一个函数时,无论在什么地方调用,这里的this都指向全局对象,所以obj.c输出为40,而obj.fn()在对象中,被对象所拥有,内部的this就指向该对象;
若把obj放在函数中返回,则遵循以上结论,当obj在函数环境中声明时,这个this在严格模式下指向undefined,非严格模式下自动转向全局对象,可运行如下例子查看区别:
var a=20;
function foo(){
var obj={
a:10,
c:this.a+20,
fn:function(){
return this.a;
}
}
return obj.c; //40
// return obj.fn; 10
}
console.log(foo()) //40
如下加深理解:
var a=20;
var foo={
a:10,
getA:function(){
return this.a;
}
}
console.log(foo.getA()) //10
var test = foo.getA;
console.log(test());//20 test()作为调用则尽管与foo.getA的引用相同,但它是独立调用,非严格模式下自动指向全局;
修改
var a=20;
function getA(){
return this.a;
}
var foo={
a:10,
getA:getA
}
console.log(foo.getA()); //10
独立调用:
function foo(){
console.log(this.a)
}
function active(fn){
fn(); //真实调用者,为独立调用
}
var a=20;
var obj={
a:10,
getA:foo
}
active(obj.getA) //20
手动设置this指向的问题:
使用call,apply显示指定this
JavaScript内部提供了一种机制,让我们可以自行手动设置this的指向。它们就是call与apply。所有的函数都具有着两个方法。它们除了参数略有不同,其功能完全一样。它们的第一个参数都为this将要指向的对象。
如下例子所示。fn并非属于对象obj的方法,但是通过call,我们将fn内部的this绑定为obj,因此就可以使用this.a访问obj的a属性了。这就是call/apply的用法。
function fn(){
console.log(this.a);
}
var obj={
a:10
}
fn.call(obj)//10
而call与applay后面的参数,都是向将要执行的函数传递参数。其中call以一个一个的形式传递,apply以数组的形式传递。这是他们唯一的不同。
function fn(num1,num2){
console.log(this.a+num1+num2);
}
var obj={
a:10
}
fn.call(obj,100,100) //210
fn.apply(obj,[20,20]) //50
call()和apply将类数组转成数组
function exam(a,b,c,d){
var arg=[].slice.call(arguments);
console.log(arg);
}
exam(1,3,4,5) // [1,3,4,5]
//也常常使用该方法将DOM中的nodelist转换为数组
// [].slice.call( document.getElementsByTagName('li') );
根据自己的需要灵活修改this指向
var foo={
name:'zx',
showname:function(){
console.log(this.name)
}
}
var fn={
name:'zqq'
}
foo.showname.call(fn) //zqq
实现继承
var person={
init:function(name,age){
this.name=name;
this.age=age;
this.gender=['man','woman']
}
}
var student={
init:function(name,age,hight){
person.call(this,name,age);
this.hight=hight;
}
}
student.prototype.message=function(){
console.log('name:'+this.name+', age:'+this.age+', high:'+this.high+', gender:'+this.gender[0]+';')
}
new student.init('xiaom', 12, '150cm').message()
简单给有面向对象基础的朋友解释一下。在Student的构造函数中,借助call方法,将父级的构造函数执行了一次,相当于将Person中的代码,在Sudent中复制了一份,其中的this指向为从Student中new出来的实例对象。call方法保证了this的指向正确,因此就相当于实现了基层。Student的构造函数等同于下。
var Student = function(name, age, high) {
this.name = name;
this.age = age;
this.gender = ['man', 'woman'];
// Person.call(this, name, age); 这一句话,相当于上面三句话,因此实现了继承
this.high = high;
}
在向其他执行上下文的传递中,确保this的指向保持不变
如下面的例子中,我们期待的是getA被obj调用时,this指向obj,但是由于匿名函数的存在导致了this指向的丢失,在这个匿名函数中this指向了全局,因此我们需要想一些办法找回正确的this指向。
var obj = {
a: 20,
getA: function() {
setTimeout(function() {
console.log(this.a)
}, 1000)
}
}
obj.getA();
常规的解决办法很简单,就是使用一个变量,将this的引用保存起来。我们常常会用到这方法,但是我们也要借助上面讲到过的知识,来判断this是否在传递中被修改了,如果没有被修改,就没有必要这样使用了。
var obj = {
a: 20,
getA: function() {
var self = this;
setTimeout(function() {
console.log(self.a)
}, 1000)
}
}
另外就是借助闭包与apply方法,封装一个bind方法。
function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments);
}
}
var obj = {
a: 20,
getA: function() {
setTimeout(bind(function() {
console.log(this.a)
}, this), 1000)
}
}
obj.getA();
当然,也可以使用ES5中已经自带的bind方法。它与我上面封装的bind方法是一样的效果。
var obj = {
a: 20,
getA: function() {
setTimeout(function() {
console.log(this.a)
}.bind(this), 1000)
}
}