JS中this指向

Mark下,闭包中this指向问题:

一、无块级作用域

function fn1()
{
for (var i = 0; i < 5; i++) {
//do somthing
}
if(false)
{
var j=5;
}
console.log(i);
console.log(j);
}
fn1();

以上代码相信大家都写过,大家觉得结果是什么呢,是undefined和报错吗,no,先来上结果

结果是5和undefined,在C语言及其它语言中,花括号里面定义的变量是对外不可见的,但是在js中却不是,在js中是以function为单位的,那是因为在函数执行的时候会有一个活动对象,

作用域链:

为什么会有作用域链,那是因为js与其它语言不同,在其它语言中是不能在函数中定义函数的,但是在js中却可以,比如下面这样:

function fn1()
{
var i=5;
function fn2()
{
var c=1;
}
}
在上面这种情况,fn2里面是可以访问fn1里面定义的i的,那是为什么呢,那是因为在执行阶段的时候,每个执行环境会为函数分配一个变量对象,比如fn2的变量对象上面存的是c,arguments,arguments是js动态添加的。而fn1的变量对象有i,fn2,arguments,那fn2怎么访问fn1里面的i的呢,其实这个变量对象只是一部分,在执行的时候,函数会创建一个作用域链对象,这个对象可以看作是一个类数组的形式,我就稍微画一下fn2的作用域链。

画得比较丑,嘿嘿,正如大家所见,fn有一个指针指向了作用域链这个类数组的东西,首先它会找到作用域链的0的位置,也就是本函数的变量对象,里面的属性会包含所有在function里面用var声明的变量,不管是在if里面,还是在for里面,只要不是在里面的function里面,都会变成变量对象的属性,这就是预编译过程,并且会为每个变量赋初值为undefined,当然还会有一些变量,比如形式参数(如果设置了形参的话,其实和argument[0],argument[1]。。。等是一个变量,也可以互相修改),如果没找到,那么继续往上找,找到作用域链的1的位置上,也就是fn1的变量对象,这里的arguments是访问不到的,因为在fn2的变量对象上已经找到了,然后在作用域链的1的位置上还是找不到的话,就继续往上找,找到全局对象上,也就是windows对象上,访问它的属性。我们来稍微分析一下arguments。

function fn1()
{
var i=5;
var ar=arguments;
function fn2()
{
var c=6;
console.log(ar);
}
return fn2;
}
var fn=fn1(1);
fn();

在fn1里面,我把fn1的arguments赋值给了ar,然后在fn2里面打印了出来,证明结果是有的,这个callee指向函数本身大家都知道,跟fn1.prototype.constructor一样,都是指向本身函数,当然这里不讨论这个。这就是传说中的闭包,把一个函数返回出去,然后这个函数的作用域链指针指向fn1也就是外层函数的变量对象,所以在这里,fn1里面的i是一直存在内存里的,因为在全局环境里,有对fn1的变量对象的引用,要消除引用,可以用

fn=null的形式,这样就断开了对内存函数的引用,自然就断开了fn1的变量对象的引用,js垃圾回收机制就会自动回收。要注意,这里的作用域链是在定义的时候就冥冥之中确定好了的,只是在执行的时候才分配内存而已,我们可以验证一下:

function fn1()
{
var i=5;
function fn2()
{
var c=6;
console.log(k);
}
return fn2;
}
(function(){
var k=5;
var fn=fn1();
fn();
})();

有些人会认为这里的结果是5,但是结果却是出错,因为在作用域链里面根本找不到这个k的定义。
到这里,大家应该对作用域链差不多有一定的了解了

再来看看this指向,this指向恰好跟虽然是对象中才出现的名词,但是也出现在函数中,因为函数本身也是一个对象,谁执行,那么this就指向谁,在函数里,默认是window对象执行,所以可以看作是window对象的方法,所以也就是window了。

验证验证:

function fn1()
{
console.log(this);
}
fn1();

大家看到了,结果是window,那我们换一种方式呢。

function fn1()
{
console.log(this);
}
var object=new Object();
object.fn1=fn1;
object.fn1();

大家看到了,居然是object,也就是this对象随着执行对象的改变,是可以改变的,我们再来看看什么叫函数表达式,什么叫方法。

function F()
{
this.data=”5”;
this.say=function()
{
alert(this.data);
}
}
var a=new F();
var c=a.say;
c();

大家看到了,我只是将对象的方法赋值给了c,然后执行,就找不到data属性了,那是因为这个赋值表达式其实默认已经将this指向给修改了,现在say方法里面的this已经指向window了,而这个c就是函数表达式,可以这样想,window的方法就叫函数表达式吧,而其它对象的方法就叫做方法,它们的this指向不一样。那我们继续跟作用域链扯上关系试试:

function F()
{
this.data=”5”;
var data2=”F”;
this.say=function()
{
console.log(data2);
console.log(this.data);
}
}
var a=new F();
var c=a.say;
c();

这个结果大家明白是为什么吗,虽然函数被绑定到了windows对象上,但是作为函数,say方法在冥冥之中已经注定了作用域链,不管绑定给谁,或者在哪执行,作用域链已经确定了,改变不了,所以可以打印出data2,写到这里,我们再来看看一些比较迷惑的写法:

function F()
{
this.data=”5”;
this.say=function()
{
console.log(this.data);
}
}
var a=new F();
(a.say)();

这里结果是5,我就不传了,我们再来看看另外一种。
function F()
{
this.data=”5”;
this.say=function()
{
console.log(this.data);
}
}
var a=new F();
(a.say=a.say)();

这里只有最后一行改变了,但是结果却是undefined,那是因为在前面的括号里面执行赋值语句话,返回的将是函数表达式,也就是绑定到了window对象上去了。再来看看令人迷惑的setTimeout
function F()
{
this.data=”5”;
this.say=function()
{
console.log(this.data);
}
}
var a=new F();
setTimeout(a.say,10);

正如大家所见,在setTimeout里面也变成了函数表达式,this指向也变成了window,那我们再换一换:

function F()
{
this.data=”5”;
this.say=function()
{
console.log(this.data);
}
}
var a=new F();
setTimeout(function(){
a.say();
},10);

为什么这里就能正确打印出来呢,那是因为在外层是个函数表达式没错,但是在这个表达式内部却没有将say方法变成函数表达式,而是让其执行,当然绑定的是a对象了,就像我之前写的第一篇博客一样,第一篇博客写的不是很好,因为那个时候没有理解到这里。我们再温习一下:

function F()
{
this.data=”5”;
this.say=function()
{
console.log(this.data);
}
this.say2=function(){
setTimeout(this.say,10);
}
}
var a=new F();
a.say2();

类似于这样,最后打印的结果是undefined,为什么是这样呢,首先在say2方法里面这个this还是绑定在a对象上的,因为在外层调用的时候没有使用函数表达式,也就是this.say是可以找到的,如果换成var c=a.say2;c();这样执行的话,连setTimeout里面的say方法都是找不到的,因为绑定到window上去了,这里是可以找到say方法的,但是say方法里面的this是那个对象呢,刚刚说了setTimeout里面会变成函数表达式,也就是window,那么this.data肯定是找不到的,那为什么会发生这样的情况呢,就是为什么会产生函数表达式这种说法呢,来看看下面这个例子:
function exec(fn)
{
fn();
}
function F()
{
this.data=”5”;
this.say=function()
{
console.log(this.data);
}
this.say2=function(){
setTimeout(this.say,10);
}
}
var a=new F();
exec(a.say);

这里,我定义了一个exec方法,然后在exec方法里面执行这个函数,然后就变成了undefined,那是因为传参的时候,是传的函数表达式,也就是不知不觉就改变了this指向,说到这里,大家应该对this指向也比较了解了,以前我经常将作用域链和原型链搞混,经过自己分析之后,妈妈就再也不用担心我的学习了。结尾我们再来看看一个牛逼的方法,bind方法,我们来看看bind方法的定义。
function bind(obj,fn)
{
return function(){
fn.apply(obj,arguments);
}
}
这里将一个方法绑定到一个对象上,你也可以用赋值语句来实现
function bind(obj,fn)
{
obj.fn=fn;
}

这样也绑定上去了,但是这样不就多了一个属性么,这样不好,上面那个绑定却是返回一个函数,这个函数可以绑定给任何对象,我们来看看绑定的例子
var div1=document.getElementById(“div1”);
function F()
{
this.data=”5”;
this.say=function()
{
console.log(this.data);
}
}
var a=new F();
div1.οnclick=a.say;

结果是undefined,那是因为this对象被绑定到div1这个dom对象上了,但是我还是想它弹出5,应该怎么办呢?我们可以借用seTimeout的思想

div1.οnclick=function(){
a.say();
};

这样就行了,因为这样this指针就是绑定在a对象上面的,当然还能这样:
var div1=document.getElementById(“div1”);
function F()
{
this.data=”5”;
this.say=function()
{
console.log(this.data);
}
}
var a=new F();
div1.οnclick=bind(a,a.say);

function bind(obj,fn)
{
return function(){
fn.apply(obj,arguments);
}
}

结果还是5,那是因为返回了一个函数表达式,里面用了一个apply修改了fn的this指向,至于你想用第一种还是第二种,那就看你自己了,也分情况,如果这个方法是这个对象上存在的,就推荐第一种,如果是这个方法对象上不存在,那么就推荐第二种,我们来看看什么时候用第二种:

var div1=document.getElementById(“div1”);
function F()
{
this.name=5;
this.data=”5”;
this.say=function()
{
console.log(this.data);
}
}
function sayName()
{
alert(this.name);
}
var a=new F();
div1.οnclick=bind(a,sayName);

function bind(obj,fn)
{
return function(){
fn.apply(obj,arguments);
}
}

这个sayName是函数或者其它对象上的,我想绑定在a对象上,那这个时候用bind就非常好了,如果是本对象存在的方法调用,那就用第一种吧,好了,今天就分析到这里了,谢谢大家!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值