JavaScript:万恶的 this 拿命来(三)

闭包 this

执行上下文决定了变量做用域javascript

闭包,它实际上是一种决策,是一种模式,让咱们能够灵活的改变变量做用域html

按惯例,上栗子java

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函数内部做用域this

备注:以上前提是基于用var声明变量,省略var声明变量会致使变量提高!经过这个栗子能够初看出做用域的端倪编码

优势VS缺点

优势:prototype

  • 合理的造成"管辖区",即"管辖区"内它能被访问到,"管辖区"外没这人
  • 不污染外层做用域

缺点

  • 由于受到了"管辖",致使有时须要访问它时却访问不到

闭包

引自阮一峰老师的博客 -- 学习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之间的剧情,缘由及解决方案
  • 细说了当即执行的函数表达式的概念及原理
  • 列举了其应用场景
相关文章
相关标签/搜索