(60)Wangdao.com第十天_JavaScript 函数_做用域_闭包_IIFE_回调函数_eval

函数        html

实现特定功能的 n 条语句封装体。c++

 

1. 建立一个函数对象面试

var myFunc = new Function();    // typeof myFunc 将会打印 function
  • 将要封装的代码以字符串的方式传递给构造函数
    • var aFunc = new Function("console.log('Hello!');");
      console.log(aFunc);
      // 打印:
          "
              function anonymous(){
                  console.log("Hello!");
              }           
          "
  • 使用函数声明来建立一个函数
    • function myFunc(){
          console.log("Hello myFunc !");  
      }
      // 会 函数声明 提高
  • 使用函数表达式来建立一个函数
    • var myFunc = function(){
          console.log("Hello myFunc ! ");
      };
      // 会 变量声明 提高

       

    • var myAdd = function(a,b){                // 传递多个参数使用,隔开
          console.log(a+b); 
      };

      注意:函数解析器不会检查实参的类型;    多余的参数将不会被使用;    缺乏的参数定义为 undefined数组

2. 函数的返回值        外界须要函数处理的值安全

使用 return 关键字返回指定结果,并结束函数。闭包

不写 return 默认为 return ;        此时的函数返回值为 undefinedapp

 

3. 函数的参数        当没法肯定 n 个变量的值时, 使用 n 个参数 传递____形参异步

  • 函数的 length 属性返回函数预期传入的参数个数,即函数定义之中的参数个数
    • length 属性就是定义时的参数个数。无论调用时输入了多少个参数,length 属性始终等于 形参个数
      function f(a, b){}
      f.length    // 2
  • 同名参数
    • 若是有同名的参数,则取最后出现的那个值
      function f(a, a) {
        console.log(a);
      }
      
      f(1, 2)    // 2
      f(1)    // undefined
      
      // 这时,若是要得到第一个a的值,可使用arguments对象。
      function f(a, a) {
        console.log(arguments[0]);
      }
      
      f(1)    // 1

只有函数调用时,才能动态肯定 this 的指向函数

.call(obj, 12, 13);工具

.apply(obj, [12, 13]);

 

.bind(obj, 12, 13);         重构建函数

不会当即执行函数,而是会建立一个新函数,新函数的 this 指向 obj,参数也被传递

也不会修改原函数的 this

  • arguments 实参列表对象             只有数组的 length 属性,可以经过 index 读写数据
    • 因为 JavaScript 容许函数有不定数目的参数,因此须要一种机制,能够在函数体内部读取全部参数。
    • 包含了函数运行时的全部参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推。这个对象只有在函数体内部,才可使用。
      var f = function (one) {
        console.log(arguments[0]);
        console.log(arguments[1]);
        console.log(arguments[2]);
      }
      
      f(1, 2, 3)
      // 1
      // 2
      // 3
      • 正常模式下,arguments对象能够在运行时修改。
        var f = function(a, b) {
          arguments[0] = 3;
          arguments[1] = 2;
          return a + b;
        }
        
        f(1, 1)    // 5
      • 严格模式下,arguments对象是一个只读对象,修改它是无效的,但不会报错。
        var f = function(a, b) {
          'use strict';    // 开启严格模式
        
          arguments[0] = 3;    // 无效
          arguments[1] = 2;    // 无效
          return a + b;
        }
        
        f(1, 1)    // 2
    • 经过 arguments 对象的 length 属性,能够判断函数调用时到底带几个参数。
    • 虽然 arguments 很像数组,但它是一个对象。数组专有的方法(好比 slice 和 forEach),不能在 arguments 对象上直接使用。
      • 若是要让arguments对象使用数组方法,真正的解决方法是将arguments转为真正的数组。下面是两种经常使用的转换方法:
        //slice方法
        var args = Array.prototype.slice.call(arguments);
        
        //逐一填入新数组。 var args = []; for (var i = 0; i < arguments.length; i++) { args.push(arguments[i]); }
    • arguments.callee 属性,返回它所对应的原函数
      • var f = function () {
          console.log(arguments.callee === f);
        }
        
        f()    // true
        // 能够经过arguments.callee,达到调用函数自身的目的。
        // 这个属性在严格模式里面是禁用的,所以不建议使用。
  • 实参能够是任意数据类型
  • 函数参数不是必需的,Javascript 容许省略参数
    • 运行时不管提供多少个参数(或者不提供参数),JavaScript 都不会报错。省略的参数的值就变为 undefined
    • 可是,没有办法只省略靠前的参数,而保留靠后的参数。若是必定要省略靠前的参数,只有显式传入 undefined
      function f(a, b) {
          return a;
      } 
      
      f( , 1)    // 报错 SyntaxError: Unexpected token ,(…)
      f(undefined, 1)    // undefined
  • 函数被称为 "第一等公民"
    • 将函数看做一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可使用值的地方,就能使用函数。
  • 当数据有规律,有必要的话能够存到一个对象里,传入函数。
    • var sun = {
                         name:"孙悟空",
                         gender:"男",
                         age:600
                     };
      
      function sayHello(obj){
          document.write("你们好,我是"+obj.name+", "+obj.gender+", "+obj.age+"岁了");
      }
      
      sayHello(sun);
  • 实参还能够是函数
    • function haha(someFunc){
          someFunc();
      }
      
      haha(sayHello);
  • 返回值能够对象
  • 返回值也能够是函数
  • break;    用于终止函数
  • continue;    用于终止本次循环,直接开始下一次循环
  • return;    用于设置返回值,也会立刻结束函数

 


 4. 函数的属性和方法

  • .name 属性,返回函数的名字
    • function f1() {}
      f1.name     // "f1"
      
      
      var f2 = function () {};
      f2.name     // "f2"
      
      
      var f3 = function myName() {};
      f3.name    // 'myName'

       

  • .toString() 方法返回一个字符串,内容是函数的源码
    • function f() {
        a();
        b();
        c();    // 这是注释
      }
      
      f.toString()
      // function f() {
      //  a();
      //  b();
      //  c();    // 这是注释
      // }

5. 做用域(scope)   

变量存在的范围。

不一样做用域的同名变量不会冲突

  • 在 ES5 的规范中,Javascript 只有两种做用域:
    • 全局做用域    全局变量(global variable)
      • 在函数外部声明的变量,变量在整个程序中一直存在,全部地方均可以读取,甚至能够在函数内部读取;
    • 函数做用域    局部变量(local variable)
      • 在函数内部定义的变量,变量只在函数内部存在,外部没法读取。
      • 做用域 是静态的 在函数建立时,就已经肯定了,并不在变化,且一直存在
      • 函数上下文 是动态的,在调用函数时建立并肯定,在函数执行完了就被释放
      • var v = 1;
        
        function f(){
            var v = 2;
            console.log(v);
        }
        
        f()    // 2
        v    // 1
      • 只有函数做用域 和 全局做用域 ,没有其余做用域
  • 必会面试题
  • function Foo() {
            getName = function () { alert (1); };
            return this;
        }
        Foo.getName = function () { alert (2);};
        Foo.prototype.getName = function () { alert (3);};
        var getName = function () { alert (4);};
        function getName() { alert (5);}
    
    
        //请写出如下输出结果:
        Foo.getName();
        getName();
        Foo().getName();
        getName();
        new Foo.getName();
        new Foo().getName();
        new new Foo().getName();
  • 分析:
  •     var getName;
        window.getName = function () { alert (4);};
        
        function Foo() {    // window.Foo
            getName = function () { alert (1); };    // 覆盖 window.getName
            return this;
        };
        window.Foo.prototype.getName = function () { alert (3);};
        window.Foo.getName = function () { alert (2);};
        
    
    
    //请写出如下输出结果:
        Foo.getName();    // window.Foo.getName()    2
        getName();    // window.getName()    4
    
        // this.getName()    window.getName()    1    注意是在何时被覆盖的
        Foo().getName();
    getName();
    // window.getName() 1 new Foo.getName(); // window.Foo.getName() 2 // 注意 new 返回的是对象 直接调用返回看return // f.getName() window.f.getName() window.Foo.prototype.getName 3 new Foo().getName();
    new new Foo().getName(); // new window.f.getName() 3
  • ES6 又新增了块级做用域

 6. 闭包 closure

 嵌套: 做为函数里面的 函数 fn2

② fn2 引用了外部函数的局部变量

外部函数 被调用执行时产生闭包,在 new 的时候也至关于执行了____实质是在 第一个知足以上条件 的函数被解析到是,产生闭包

此时就产生了一个闭包____ fn2.Scopes[0] = fn2.closure————包含了被引用局部变量的一个"对象",存在于嵌套的内部函数中,可使用调试工具查看到。

其中 [[Scopes]] 是由底层 c/c++ 实现

  • 一个外层函数,嵌套多个函数,这几个内部函数 共用一个 闭包,该闭包不会在外层函数执行完后销毁,也就意味着延长了变量的存活时间
  • 闭包内的变量,没法直接操做,只能经过内层函数间接操做闭包内的变量
  • 闭包存在于 内部函数 fn2 中, 只要 fn2 存在,则闭包存在____当再也没有 变量 指向这个 fn2 ,则 fn2 不存在,闭包也就不存在了

缺点:

因为闭包的存在,未释放,致使占用内存时间变长。易形成内存泄漏

怕的就是出现 很是多个闭包

解决:

能不用闭包就不用闭包

及时释放____让 指向含闭包函数的 变量,指向 null

  • 做用有
    • 使得能够 在函数外部 间接操做 函数内部的变量。
    • 让这些变量始终保持在内存中, 不会在函数调用结束后,被垃圾回收机制回收。
    • 封装对象的私有属性和私有方法。
      • function Person(name) {
            var _age;
            function setAge(n) {
                _age = n;
            }
            function getAge() {
                return _age;
            }
        
            return {
                name: name,
                getAge: getAge,
                setAge: setAge
            };
        }
        
        var p1 = new Person('张三');
        p1.setAge(25);    // 操做的是 闭包中的 _age
        p1.getAge()    // 访问的是 闭包中的 _age
        // 上面代码中,函数的内部变量,经过闭包和,变成了返回对象的私有变量。
        Person_agegetAgesetAgep1

        注意

        • 外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,因此内存消耗很大。

        • 所以不能滥用闭包,不然会形成网页的性能问题。

  • 是Javascript 语言的一个难点,也是它的特点。
  • 闭包最大的特色,就是它能够 “记住” 诞生的环境,好比 fn2 记住了它诞生的环境 fn1 的内部变量。在本质上,闭包就是将函数内部和函数外部链接起来的一座桥梁。
  • 不少高级应用都要依靠闭包实现。 
  • 理解闭包,首先必须理解变量做用域。
  • "做用域链"(scope chain)
    • JavaScript 语言特有的结构
    • 子对象会一级一级地向上寻找全部父对象的变量。因此,父对象的全部变量,对子对象都是可见的,反之则不成立。

 

  • 正常状况下,获得函数内的局部变量是办不到的
    • 只有经过变通方法才能实现
    • 那就是在函数的内部,再定义一个函数。
    • 既然子函数能够读取父函数的局部变量,那么只要把子函数做为返回值,咱们不就能够在父函数外部读取它的内部变量了吗!
      function f1() {
        var n = 999;
        function f2() {
          console.log(n);
        }
        return f2;
      }
      
      var result = f1();
      result(); // 999
      // 函数的返回值就是函数,因为能够读取的内部变量,因此就能够在外部得到的内部变量了
      f1f2f2f1f1
    • 闭包就是返回的 子函数 f2,即可以读取其余函数内部变量的函数。

 

  •  请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。
    • function createIncrementor(start) {
          return function () {
              return start++;
          };
      }
      
      var inc = createIncrementor(5);    // 执行了 外层函数,而后销毁了 函数上下文,即 全部外层函数的变量都销毁了
      
      inc();    // 5
      
      inc();    // 6
      
      inc();    // 7

       

 


7. 当即调用的函数表达式(IIFE)

 自调用匿名函数

____封装时, 隐藏函数内部具体实现, 防止外部修改函数内的代码

____防止污染外部命名空间

    • 以圆括号开头,引擎就会认为后面跟的是一个表示式,而不是函数定义语句,避免出现错误。

这就叫作“当即调用的函数表达式”(Immediately-Invoked Function Expression),简称 IIFE。

    • 目的有两个:
      • 一是没必要为函数命名,避免了污染全局变量;
      • 二是 IIFE 内部造成了一个单独的做用域,能够封装一些外部没法读取的私有变量。
        // 写法一
        var tmp = newData;
        processData(tmp);
        storeData(tmp);
        
        
        // 写法二    更好,由于彻底避免了污染全局变量。
        (function(w){    // 
            var tmp = newData;
            processData(tmp);
            storeData(tmp);
        }(window));    // 方便对代码进行压缩, 变量 window 改为 w, 减少代码体积
  • 圆括号()是一种运算符,跟在函数名以后,表示调用该函数。    好比,isNaN() 就表示调用 isNaN 函数。
  • 当 function 出如今行首,为了避免让引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里面。

 


 8. 回调函数

自定义的,不被本身调用的,达到某种条件后被某对象调用了。

异步加载通知机制: 

全部异步代码,不能当即执行

只有等主进程执行完全部代码后,才会执行 异步代码

  • setTimeout(function(){
        console.log("异步代码");
    }, 0);
    
    console.log("主进程代码");
    // 打印:
    // 主进程代码
    // 异步代码

 


 9. eval 命令

接受一个字符串做为参数,并将这个字符串看成语句执行。


  • eval('var a = 1;');
    a    // 1
  • 若是参数字符串没法看成语句运行,那么就会报错。    // Uncaught SyntaxError: Invalid or unexpected token
  • 若是eval的参数不是字符串,那么会原样返回。
  • eval 没有本身的做用域都在当前做用域内执行,所以可能会修改当前做用域的变量的值,形成安全问题。
  • JavaScript 规定,若是使用严格模式eval内部声明的变量,不会影响到外部做用域。
    • 不过,即便在严格模式下,eval依然能够读写当前做用域的变量。
      (function f() {
        'use strict';
        var foo = 1;
        eval('foo = 2');
        console.log(foo);  // 2
      })()

      因此通常不推荐使用。一般状况下,eval 最多见的场合是解析 JSON 数据的字符串,不过正确的作法应该是使用原生的JSON.parse方法

  • eval 的别名调用的形式五花八门,只要不是直接调用,都属于别名调用,由于引擎只能分辨eval()这一种形式是直接调用。
    eval.call(null, '...');
    window.eval('...');
    (1, eval)('...');
    (eval, eval)('...');
    
    // 上面这些形式都是eval的别名调用,做用域都是全局做用域。

     


内存溢出

须要的内存,大于现有内存

内存泄漏

资源占有一片空间,而不曾释放,致使这片内存没法被使用

及时 obj = null; 来释放内存

  • 意外的全局变量

在函数中, 未使用 var 关键字定义变量, 致使直接声明了一个全局变量

  • 没有及时清理的 定时器 或者 回调函数

 

  • 闭包

 


 

10. 属性描述符

相关文章
相关标签/搜索