zoukankan      html  css  js  c++  java
  • javascript 高级特性探讨A1-A3(作用域-上下文)

    1 if (value) {
    2     var somevar = 'value';
    3 }
    4 console.log(somevar); //输出value

    这是因为javascript的作用域完全是由函数来决定的,if, for 语句中的花括号不是独立的作用域

    A1.1函数作用域

    不同于大多数类c的语言,由一对大括号封闭的代码块就是一个作用域,js的作用与就是通过函数来定义的,在一个函数中定义的变量只对这个函数内部可见,我们称为函数作用域,在函数中引用一个变量时,js会先搜索当前函数作用域,或者称为‘局部作用域’,如果没有找到则搜索其上层作用域,一直到全局作用域,我们来看一个简单的例子。

     1 var v1 = 'v1';
     2 var f1 = function () {
     3     console.log(v1); //输出v1
     4 };
     5 f1();
     6 var f2 = function () {
     7     var v1 = 'local';
     8     console.log(v1); //输出 local
     9 };
    10 f2();

    以上例子十分明了,js的函数定义是可以嵌套的,每一层都是一个作用域,变量的搜索顺序是从内到外,下面这个例子可能就会有些令人困惑:

    1 var scope = 'global';
    2 var f = function () {
    3     console.log(scope); //输出 undefined
    4     var scope = 'f';
    5 };
    6 f();

    上面的的代码可能和你预想的不一样,没有输出global,而是undefined,这是为什么呢,这是js的一个特性,按照作用域搜索顺序,在console.log函数访问scope变量时,js会先搜索函数f的作用域,恰巧在f的作用域里面搜索到scope变量,所以上层作用域定义的scope就被屏蔽了,但是执行到console.log语句时,scope还没被定义,或者说初始化,所以得到的就是undefined的值了

       我们还可以从另一个角度去理解,对于开发者来说,在访问未定义的变量或者定义了但没有初始化的变量时,获得的值都是undefined,于是我们可以认为,无论在函数内部什么地方的变量,在一进入函数时就被定义了,但直到var所在的那一行它才被初始化,所以在这之前引用到的都是undefined值。(事实上,js的内部实现并不是这样的,未定义变量和值为undefined的变量还是有区别的。)

     1 var f = function () {
     2     var scope = 'f0';
     3     (function () {
     4         var scope = 'f1';
     5         (function () {
     6             console.log(scope); //输出f1
     7         })();
     8     })();
     9 };
    10 f1();

    我们在最上层函数引用了scope变量,通过作用域搜索,找到了其父作用域中定义的scope变量,有一定需要注意:函数作用域的嵌套关系是定义时决定的,而不是调用时决定的,也就是说,js的作用域是静态作用域,这是因为作用域的嵌套关系可以在语法分析时确定,而不必等到运行时确定,下面我们看看这个例子:

     1 var scope = 'top';
     2 var f1 = function () {
     3     console.log(scope);
     4 };
     5 f1(); //输出top
     6 var f2 = function () {
     7     var scope = 'f2';
     8     f1();
     9 };
    10 f2(); //输出top

    这个例子中,通过f2调用的f1在查找scope定义时,找到的父作用域中定义的scope变量,而不是f2中定义的scope变量,这说明了作用域的嵌套关系不是在调用时确定的,而是在定义的时候确定.

    A1.2全局作用域

     在js中有一种特殊对象是全局对象,全局作用域中的变量不论在什么函数中都可以被直接引用,而不必通过全局对象.

    符合以下条件的变量属于全局作用域:

    1.在最外层定义的变量

    2.全局对象的属性

    3.任何地方隐式定义的变量(未定义直接赋值的变量)

    需要格外注意的是第三点,在任何地方隐式定义的变量都会定义在全局作用域中,即不通过var声明直接赋值的变量,这一点经常被人遗忘,而模块化编程的一个重要原则就是避免使用全局变量,所以我们在任何地方都不应该隐式定义变量

    A2闭包

    闭包的严格定义时‘由函数(环境)及其封闭的自由变量组成的集合体。’,这个定义或许我们暂时不能够理解,让我们直接看例子

     1 var gen = function () {
     2     var count = 0;
     3     var get = function () {
     4         count ++;
     5         return count;
     6     };
     7     return get;
     8 };
     9 var counter = gen();
    10 console.log(counter()); //输出1
    11 console.log(counter()); //输出2
    12 console.log(counter()); //输出3

    这段代码中,gen函数中有一个局部变量count,初始值为0,还有一个叫做get的函数,get将其父作用域,也就是gen中的count变量加1,并返回count的值,gen函数的返回值就是get函数,在外部我们通过count变量调用了gen函数并获取了它的返回值,也就是get函数,接下来反复调用几次counter(),我们发现每次返回的值都递增了1

      按照我们以前的函数式编程思维的理解,count是gen函数内部的变量,它的生命周期就是gen被调用的时期,当gen从调用栈中返回时,count变量申请的空间也就被释放,问题是,gen调用结束后,counter()却顶用了‘已经被释放了的’ count变量,而且非但没有出错,反而每次调用counter()时还修改并返回了count,这是怎么回事呢

    这正是闭包的特性,当一个函数返回它内部定义的一个函数时,就产生了一个闭包,闭包不但包括被返回的函数,还包括这个函数的定义环境,上面例子中,当函数gen()的内部函数gen被一个外部变量counter引用时,counter和gen()的局部变量就是一个闭包,如果还不够清晰,下面我们再来看看这个例子

     1 var gen = function () {
     2     var count = 0;
     3     var get = function () {
     4         count ++;
     5         return count;
     6     };
     7     return get;
     8 };
     9 var counter1 = gen();
    10 var count2 = gen();
    11 console.log(counter1()); //输出1
    12 console.log(counter2()); //输出1
    13 console.log(counter1()); //输出2
    14 console.log(counter1()); // 输出3
    15 console.log(counter2()); //输出2

    上面这个例子我们就看的比较清楚了,counter1和counter2分别调用了gen()函数,生成了二个闭包的实例,它们内部引用的count变量分别属于各自的运行环境,我们可以理解成,在gen函数中返回get函数时,私自将get可能引用的到的gen()函数的内部变量(也就是count) 也返回了,并且在内存中生成了一个副本,之后gen()返回的函数的二个实例counter1和counter2就是相互独立了的

    闭包的用途:

    一。嵌套的回调函数

    1.是实现嵌套的回调函数,2是隐藏对象的细节

    二。实现私有成员

    js通过约定在所有私有属性前加上下划线(例如_myoldboy),表明这个属性是私有的,外部对象不应该直接读写它,但这只是个非正式的约定,假设对象的使用者不这么做,有没有更严格的机制呢,答案是有的,那就是闭包,让我们来瞅瞅真正的私有化吧

     1 var gen = function () {
     2     var count = 0;
     3     var get = function () {
     4         count ++;
     5         return count;
     6     };
     7     return get;
     8 };
     9 var counter = gen();
    10 console.log(counter());//输出1
    11 console.log(counter());//输出2
    12 console.log(counter());//输出3

    我们可以看到,只有调用了counter()才能访问到闭包内的count变量,并按照规则对其加1,chucizhiw绝无其他方式找到count变量,受到这个简单例子的启发,我们可以把一个对象用闭包封装起来,只返回一个‘访问器'对象,即可实现对细节的隐藏。

    A3.1上下文对象

    在js中,上下文对象就是this指针,即被调用函数所处的环境,上下文对象的作用是在一个函数内部引用调用它的对象本身,js的任何函数都是被某个对象调用的,包括全局对象,所以,this指针是一个很重要的东西.

     1 var someuser = {
     2     name : 'by',
     3     display : function () {
     4         console.log(this.name);
     5     }
     6 };
     7 someuser.display(); //输出 by
     8 var foo = {
     9     bar : someuser.display,
    10     name : 'foo'
    11 };
    12 foo.bar(); //输出foo

    js的函数式编程特性使得函数可以像一般的变量一样赋值,传递和计算,我们可以看到在上面的代码中,foo对象的bar属性时someuser.display函数,使用foo.bar()调用时,bar和foo对象的函数看起来没有区别,其中的tjis指针不属于某个函数,而是函数调用时所属的对象

      在js中,本质上,函数类型的变量是指向这个函数实体的一个引用,在引用之间赋值不会对对象产生复制行为,我们可以通过函数的任何一个引用调用这个函数,不同之处仅仅在于上下文,下面的例子或许会有更好的解释:

     1 var someuser = {
     2     name: 'by',
     3     func: function () {
     4         console.log(this.name);
     5     }
     6 };
     7 var foo = {
     8     name: 'foo'
     9 };
    10 someuser.func(); //输出by
    11 foo.func = someuser.func;
    12 foo.func(); //输出foo
    13 name = 'global';
    14 func = someuser.func;
    15 func(); //输出global

    仔细观察上面的例子,使用不同的引用来调用同一个函数时,this指针永远是这个引用所属的对象。js中的函数作用域是静态的,也就是说一个函数可见范围是在预编译的语法分析中就可以确定的,而上下文对象则可以看做是对静态作用域的补充

  • 相关阅读:
    解决Cannot download "https://github.com/sass/node-sass/releases/download/binding.nod的问题
    wid是一个字符串 必须转化成整型
    如何获取内联样式的width值
    onresize方法
    jquery中$("#afui").get(0)为什么要加get(0)呢?
    jquery $(document).ready() 与window.onload的区别
    鼠标点击
    添加二级菜单颜色
    homepage左边的导航菜单怎么做的?
    center
  • 原文地址:https://www.cnblogs.com/237325670qqcom/p/5708297.html
Copyright © 2011-2022 走看看