zoukankan      html  css  js  c++  java
  • JavaScript

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

    • 举报

  • 相关阅读:
    小程序行内点击事件冲突解决
    小程序带参返回刷新主页面
    小程序时间选择器(精确到秒)
    Bootstrap-Table事件和方法
    JS 解决txt文件直接打开而不是下载
    Bootstrap-Table进阶篇
    Bootstrap-Table入门篇
    Angular+SSM+Ajax的简单购物车实例
    Angular实现简单购物车
    K3BOS单据获取单据体行数
  • 原文地址:https://www.cnblogs.com/-ding/p/6022695.html
Copyright © 2011-2022 走看看