JavaScript - this详解 (三)
闭包 this
执行上下文决定了变量作用域
而闭包,它其实是一种决策,是一种模式,让我们可以灵活的改变变量作用域。
按惯例,上栗子
var global = 'global';function outer(){
var out = 'outer'; function middle(){
var mid = 'middle';
function inner(){
var in = 'inner';
console.log('globa : '+global, ',outer : '+out, ',middle : '+mid, ',inner : '+in); //globa : global outer : outer middle : middle inner : inner
}
inner();
console.log(in) //undefined
}
middle();
}
outer();
console.log(inner); //undefined
console.log(middle); //undefined
console.log(outer); //undefined
console.log(global); //global
作用域
抽象:不同的"函数调用"会产生不同的"执行上下文",不同的"执行上下文"划分出了不同的"变量作用域"。
具体:咱们应该见过婚礼上的蛋糕,圆形的,一圈一圈的同心圆,中间最高,最外围最低。此处的"最高"和"最低"可以理解为访问权限,及里面能访问外面,而外面访问不了里面。
变量作用域
变量在inner函数中的作用域 = inner函数
内部作用域 +所有外层的作用域
变量在middle函数中的作用域 = middle函数
内部作用域 +所有外层的作用域 - inner函数内部
变量在outer函数中的作用域 = outer函数
内部作用域 +所有外层的作用域 - middle函数内部作用域
备注:以上前提是基于用var声明变量,省略var声明变量会导致变量提升!通过这个栗子可以初看出作用域的端倪
优点VS缺点
优点:
合理的形成"管辖区",即"管辖区"内它能被访问到,"管辖区"外没这人
不污染外层作用域
缺点
因为受到了"管辖",导致有时需要访问它时却访问不到
闭包
引自阮一峰老师的博客 -- 学习Javascript闭包(Closure)
由于在
Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
所以,在本质上,闭包就是将函数
内部和函数外部连接起来的一座桥梁。
只要咱们弄明白闭包,其中的this自然跑不掉。
上栗子
function constfuncs() {
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs[i] = function () {
return i;
}
}
return funcs;
}
var funcs = constfuncs();
alert(funcs[1]());
这是最近的一个问题,对于funcs[1]()是几大家可以去试试
好吧,如果去试了可能会发现,无论你funcs[1]()中输入的时1还是9,它的都是10。
这个就有意思了,为什么不论怎么输入,结果都是10呢?如果你发出了这个疑问,那么你的潜意识里肯定是弄错了件事:你认为
funcs[i] = function () {
return i;
}
funcs[i]中的i会决定这个匿名函数中返回的i,其实不然。
在for循环的过程中,会不停的创建函数:
funcs[0] = function () {
return i;
} //对象字面量被创建...
funcs[9] = function () {
return i;
} //对象字面量被创建
被创建的函数并没有被立刻执行,而是进入了等待队列,等待你的主动调用。
于此同时,i在等于9后又执行了i++操作,现在i等于10。
好的,现在咱们调用了funcs[1](),那么下一步函数会返回i,也就是10,所以无论你调用funcs[1]()还是funcs[9](),它都会返回10。
现在改用闭包来解决这个问题了!
其实有一个值得玩味事情是:为什么遇到这样的问题,我们会用闭包解决?
换一种说法是:为什么闭包能解决这个应用场景的问题?
让我们在回顾一下那句话
在本质上,闭包就是将函数
内部和函数外部连接起来的一座桥梁。
因为我们正好需要一座桥梁,将外部的i和内部的i关联起来。
上栗子
function constfuncs() {
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs[i] = (function (i) { // 标记1
return function () { /
return i; // 标记2(上下三行)
}; /
})(i) // 标记3
} return funcs;
}var funcs = constfuncs();
console.log(funcs[1]());
- 标记2:我们在原本返回i的地方,返回了一个匿名函数,里面再返回了i
- 标记3:我们传入了i,架起了连接外部的桥梁
- 标记1:我们将标记3传入的i作为参数传入函数,架起了连接内部的桥梁
至此,每当一个for循环执行一次,i也会传入函数内部被保存/记忆下来。
再来一发
function constfuncs() {
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs[i] = (function () {
return i;
}(i));
} return funcs;
}
var funcs = constfuncs();
console.log(funcs[1]);
在这个栗子中,由于我们改变了写法,导致最后的调用方法改变,但依旧是应用闭包的特性。
如果这个栗子懂了,那闭包应该懂了一大半了,如果还是有点晕,没关系,咱们继续往下看。
this
现在咱们说说闭包和this之间的事
上栗子(浏览器/REPL中)
var name = 'outer'function Base(){}
Base.prototype.name = 'base';
Base.prototype.log = function () {
var info = 'name is ';
console.log(this.name); // name is base
function inner(){
console.log(info,this.name); // name is outer
};
inner();
};
var base = new Base();
base.log();
我们期望的是通过this访问原型对象中的name,可是最后却访问到全局对象中的name属性。
所以光有闭包还不够,我们需要借助点别的技巧,改写log函数
var name = 'outer'function Base(){}
Base.prototype.name = 'base';
Base.prototype.log = function () {
var info = 'name is ';
var self = this; // 保存this
function inner(){
console.log(info,self.name);
};
inner();
};
var base = new Base();
base.log();
注解:使用self或that变量来保存this是约定俗成
原因:
- 由于inner函数定义在了log函数内部,形成了闭包,导致内部this"泛滥"指向了全局对象,现在做的就是在this还没有"泛滥"的时候,保存它。
更常见的,是这样的改写log函数
var name = 'outer'function Base(){}
Base.prototype.name = 'base';
Base.prototype.log = function () {
var info = 'name is ';
var self = this;
(function inner(){
console.log(info,self.name);
})(self);
};
var base = new Base();
base.log();
用一个"立即执行的函数表达式"代替函数创建和调用。
再来一枚经典栗子
var scope = "global";
var object = {
scope:"local",
getScope:function(){
return function(){
return this.scope;
}
}
}
相信大家对函数中的函数应该有一定的警惕性了,this.scope的值是谁大家应该也心中有值了,大家可以自己动手改一改,实践才是王道!
立即执行的函数表达式
最常见的版本大概是长这个样子:
var name = 'outer';
(function () {
var name = 'inner';
console.log(name); // inner
console.log(this.name); // outer})();
相信大家看过上文后,应该都明白了为什么this.name会输出outer,下面来说说什么是立即执行的函数表达式。
咱们分两步说:
- 立即执行
- 函数表达式
常见的创建函数有这两种
function Thing(){
console.log('thing');
} //直接函数声明
Thing(); //函数调用
var thing = function () {
console.log('thing');
}; //函数字面量
thing(); //函数调用
不妨试试这样
thing()
你会发现函数神奇的执行了,也就是说函数名后面跟上一对小括号(),可以立刻调用函数。
那单独的那一行thing是什么呢?它是函数的名字,是一个指针,但是在这里被解析成了表达式,单独占了一行。
也就说我们通常执行函数都是这么搞的,那么万一这函数没有名字呢?我们可以这样
(function(){ console.log('no name');
})();
(function(){ console.log('no name')
}());
-function(){ console.log('no name');
}();
+function(){ console.log('no name');
}();
~function(){ console.log('no name');
}();
!function(){ console.log('no name');
}();
除了最上面两个较常见外,其他的都挺怪异!但是他们都可以立即执行!
注意函数的前面都有一个符号,
'+' , '-' , '~' , '!' , '()'这些符号告诉解析器强制把这些函数声明解析成函数表达式,最后的一对小括号()又让这函数表达式立即执行。
注意
如果要使用就请使用前两个,用小括号()的方式是最正规也是惯例,其他的方式容易导致自己或者他人误解,而且不符合编码规范,强烈不推荐使用,自己在练习的时候可以玩一玩,体会体会。
应用场景
1.你可以用立即执行的函数表达式暴露公开的成员或方法
var cal = (function () {
return {
add: function (a,b) {
return a + b;
},
sub: function (a,b) {
return a - b;
}
}
})();
cal.add(5,2) // 7
cal.sub(4,1) // 3
或者
var cal = (function () {
var way = {};
way.add = function (a,b) { return a + b;
};
way.sub = function (a,b) { return a - b;
}; return way;
})();
cal.add(3,6) // 9
cal.sub(8,5) // 3
2.续写子模块
cal.controller = (function () {
var way = {};
var result;
way.set = function (args) {
result = args;
}
way.get = function () {
return result;
}
return way;
})();
cal.controller.set(123);
cal.controller.get(); // 123
总结
细说
变量作用域,比较其优缺点举例说明
闭包的概念,作用举例吐槽了
闭包和this之间的剧情,原因及解决方案细说了
立即执行的函数表达式的概念及原理列举了其
应用场景
javascript
举报
JavaScript - this详解 (三)的更多相关文章
- JavaScript事件详解-jQuery的事件实现(三)
正文 本文所涉及到的jQuery版本是3.1.1,可以在压缩包中找到event模块.该篇算是阅读笔记,jQuery代码太长.... Dean Edward的addEvent.js 相对于zepto的e ...
- JavaScript事件详解-Zepto的事件实现(二)【新增fastclick阅读笔记】
正文 作者打字速度实在不咋地,源码部分就用图片代替了,都是截图,本文讲解的Zepto版本是1.2.0,在该版本中的event模块与1.1.6基本一致.此文的fastclick理解上在看过博客园各个大神 ...
- JavaScript正则表达式详解(一)正则表达式入门
JavaScript正则表达式是很多JavaScript开发人员比较头疼的事情,也很多人不愿意学习,只是必要的时候上网查一下就可以啦~本文中详细的把JavaScript正则表达式的用法进行了列表,希望 ...
- JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解
二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...
- JavaScript事件详解-zepto的事件实现
zepto的event 可以结合上一篇JavaScript事件详解-原生事件基础(一)综合考虑源码暂且不表,github里还有中文网站都能下到最新版的zepto.整个event模块不长,274行,我们 ...
- [原创]JavaScript继承详解
原文链接:http://www.cnblogs.com/sanshi/archive/2009/07/08/1519036.html 面向对象与基于对象 几乎每个开发人员都有面向对象语言(比如C++. ...
- javaScript基础详解(1)
javaScript基础详解 首先讲javaScript的摆放位置:<script> 与 </script> 可以放在head和body之间,也可以body中或者head中 J ...
- javascript设计模式详解之命令模式
每种设计模式的出现都是为了弥补语言在某方面的不足,解决特定环境下的问题.思想是相通的.只不过不同的设计语言有其特定的实现.对javascript这种动态语言来说,弱类型的特性,与生俱来的多态性,导致某 ...
- javaScript笔记详解(1)
javaScript基础详解 版权声明 本文原创作者:雨点的名字 作者博客地址:https://home.cnblogs.com/u/qdhxhz/ 首先讲javaScript的摆放位置:<sc ...
- 【HANA系列】【第二篇】SAP HANA XS使用JavaScript编程详解
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[HANA系列][第二篇]SAP HANA XS ...
随机推荐
- 19_java之List和Set
01List接口的特点 A:List接口的特点: a:它是一个元素存取有序的集合. 例如,存元素的顺序是11.22.33.那么集合中,元素的存储就是按照11.22.33的顺序完成的). b:它是一 ...
- 温故而知新-WebSocket 教程
一.为什么需要 WebSocket? 初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处? 答案很简单,因为 HTTP 协议有 ...
- Python 小知识点(10)--异常结构记录
try: print("try中") except KeyError as e: # 异常时,执行该块 print("异常中") else: # 主代码块(tr ...
- IE6中PNG图片背景无法透明显示的最佳解决方案
我想,对于像我这样的年轻的程序员来说,做网页开发时用chrome.firefox或者ie10什么的大约是被宠坏了.所以当最近做的项目不得不在恐龙化石般的ie6上运行时,ie6种种诡异的行径简直让我发指 ...
- SuggestFrameWork js代码结构
关于suggestFrameWork的使用教程网上很多,如果您仅仅想知道如何使用请移步.这里展现一下js代码实现结构 下载地址 http://sourceforge.net/projects/sugg ...
- 【Consul】Consul实践指导-配置文件
Agent有各种各样的配置选项,这些配置选项可以通过命令行参数的方式设定,也可用通过配置文件的方式设定--所有的配置选项都是可选的,当然也是有默认值的. 当加载配置选项时,consul是按照词典顺序从 ...
- 用vim生成一批递增ID
假设说要生成1000个以xxx开头的后面加数字的ID,比如xxx1到xxx1000.一般我们可以通过.csv去递增,然后替换,但是直接用vim也是可以达到这样的目的. 下面通过一个gif图演示这个过程 ...
- 分别用js和css实现瀑布流
下午查找了瀑布流的相关原理,找了一些css3实现的还有js实现的,最后总结了一些比较简单的,易懂的整理起来 1.css3实现 只要运用到 column-count分列 column-width固 ...
- UICamera
[UICamera] UICamera是一个命名不太好的组件.实际上,它的名字某种原因只是为了保持向后兼容.UICamera脚本实际做的事是发送NGUI事件给所有被它所附加的摄像机所看见的对象.除此之 ...
- C++ const引用
(1) 在实际的程序中,引用主要被用做函数的形式参数--通常将类对象传递给一个函数.引用必须初始化. 但是用对象的地址初始化引用是错误的,我们可以定义一个指针引用. 1 int ival ...