闭包,this指向,作用域,绑定对象
来源:互联网 发布:张卫健 西游记 知乎 编辑:程序博客网 时间:2024/05/16 17:04
说完了面向对象以及继承,今天再来说说this指向及作用域的问题,许多人都觉得js很简单,但是我学了这么多语言,并不觉得js简单呀T_T,其中难点之一就是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){<span style="white-space:pre"></span>return function(){<span style="white-space:pre"></span>fn.apply(obj,arguments);<span style="white-space:pre"></span>}}这里将一个方法绑定到一个对象上,你也可以用赋值语句来实现
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.onclick=a.say;
结果是undefined,那是因为this对象被绑定到div1这个dom对象上了,但是我还是想它弹出5,应该怎么办呢?我们可以借用seTimeout的思想
div1.onclick=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.onclick=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.onclick=bind(a,sayName);function bind(obj,fn){return function(){fn.apply(obj,arguments);}}
这个sayName是函数或者其它对象上的,我想绑定在a对象上,那这个时候用bind就非常好了,如果是本对象存在的方法调用,那就用第一种吧,好了,今天就分析到这里了,谢谢大家!
- 闭包,this指向,作用域,绑定对象
- bind(this)绑定this指向的对象、箭头函数和闭包
- 闭包、this指向
- 重新认识JS的this、作用域、闭包、对象
- js函数作用域及this指向
- Javascript This.作用域.闭包
- 作用域 闭包 this关键字
- JavaScript闭包作用域与this
- JavaScript 变量作用域、this、闭包
- JavaScript 变量作用域、this、闭包
- js 静态作用域 闭包 this
- javascript作用域和闭包,this
- 函数表达式(递归+闭包+this对象+私有作用域)
- JavaScript(面向对象+原型理解+继承+作用域链和闭包+this使用总结)
- this对象的指向
- this对象的指向
- AS3作用域规则–this指向哪里
- js函数的作用域与this指向
- ntfs readdir的速度太慢
- String 过滤字符串
- java线程扫盲笔记
- Apache Shiro 使用手册(三)Shiro 授权
- 01背包的变形
- 闭包,this指向,作用域,绑定对象
- String.format 的大用场
- 感受
- 关于Linux C++代码在Windows平台上的调试运行
- 批处理命令
- 算法与数据结构面试题(8)-判断整数序列是不是二元查找树的后序遍历结果
- atomic
- 2.QT中操作word文档
- linux c/c++插件技术:动态链接库