您的位置:首页 > Web前端 > JavaScript

JavaScript - this详解 (三)

2016-11-02 13:51 471 查看

闭包
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

举报
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: