• 读Secrets of the JavaScript Ninja(一)函数


    理解JavaScript为什么应该作为函数式

    在JavaScript中,函数是程序执行过程中的主要模块单元

    函数是第一类对象

    • 通过字面量创建
    function ninjaFunction(){}
    
    • 赋值给变量,数组项或其它对象的属性
    var ninjaFunction = function() {}
    ninjaFunction.push(function(){})
    ninja.data = function(){}
    
    • 作为函数参数来传递
    call(function(){})
    
    • 作为函数的返回值
    function returnNewNinjaFunction() {
      return function() {}
    }
    
    • 具有动态创建和分配的属性
    var ninjaFunction = function() {}
    ninjaFunction.ninja = "Hanzo"
    

    函数作为对象的乐趣

    通过向函数添加属性来实现存储函数和自记忆函数

    存储函数

    var store = {
      nextId: 1,
      cache: {},
      add: function(fn) {
      if (!fn.id) {
      fn.id = this.nextId++;
      this.cache[fn.id] = fn;
      return true;
      }}
    };
    

    记忆函数

    function isPrime(value) {
      if (!isPrime.answers) {
        isPrime.answers = {};
      }
      if (isPrime.answers[value] !== undefined) {
        return isPrime.answers[value];
      }
      var prime = value !== 0 && value !== 1;  
      for (var i = 2; i < value; i++) {
        if (value % i === 0) {
          prime = false;
          break;
        }
      }
      return isPrime.answers[value] = prime;
    }
    

    函数定义

    • 函数声明

    在声明前就可以被调用

    function myFun(){ return 1;}
    
    • 函数表达式

    必须先声明

    const func = function() {}
    
    • 箭头函数

    函数内的this从上一层作用域继承

    (myArg) => {
      return myArg*2
    }
    

    如果括号里只有一个参数就可以省略括号,箭头后只有一条语句可以省略大括号,作为这个箭头函数的返回值

    • 立即函数

    声明即调用

    (function(){})(3)
    

    函数的参数

    剩余参数

    剩余参数以...作为前缀声明,以数组形式传入函数

    function multiMax(first, ...remainingNumbers) {
      var sorted = remainingNumbers.sort(function(a, b) {
      return b – a;
    });
      return first * sorted[0];
    }
    

    默认参数

    function performAction(ninja, action = "skulking") {
      return ninja + " " + action;
    }
    

    理解函数调用

    隐含函数参数

    函数调用时还会传递两个隐式的参数: arguments和this。
    这些隐式参数在函数声明中没有明确定义, 但会默认传递给函数并
    且可以在函数内正常访问。

    arguments参数

    arguments可以访问到传给函数所有的参数,虽然arguments具有Length属性,但是它只是一个类数组结构,并不是数组

    • 操作所有参数
    function sum() {
      var sum = 0;
      for(var i = 0; i < arguments.length; i++){
        sum += arguments[i];
      }  
      return sum;
    }
    

    this参数: 函数上下文

    this参数的指向不仅是由定义函数的方式和位置决定的, 同时还严重受到函数调用方式的影响。

    函数调用

    四种调用函数的方法

    • 作为一个函数(function)——skulk(), 直接被调用。
    • 作为一个方法(method)——ninja.skulk(), 关联在一个对象上, 实现面向对象编程。
    • 作为一个构造函数(constructor)——new Ninja(), 实例化一个新的对象。
    • 通过函数的apply或者call方法——skulk.apply(ninja)或者
      skulk.call(ninja)

    1. 作为函数直接调用

    也就是在全局环境中直接调用,在严格模式下,函数内部的this应该是undefined,非严格模式下就是window

    function ninja() {};
    ninja();
    var samurai = function(){};
    samurai();  
    (function(){})()
    

    2. 作为方法被调用

    当作为方法被调用时,函数内部的this应该为调用这个函数的对象

    var ninja = {};
    ninja.skulk = function(){};
    ninja.skulk();
    

    3. 作为构造函数调用

    当函数时候new关键词作为构造函数来调用时,会发生三个动作

    1. 创建一个空对象
    2. 把这个空对象作为当前函数的this
    3. 新构造的对象作为new的返回值
    function Ninja() {
      this.skulk = function() {
        return this;
      };
    }   
    var ninja1 = new Ninja();
    var ninja2 = new Ninja();  
    

    4. 使用apply和call方法调用

    JavaScript为我们提供了一种调用函数的方式, 从而可以显式地指定任何对象作为函数的上下文。 apply、call是函数的方法,即函数在JavaScript中也是对象

    function juggle() {
      var result = 0;
      for (var n = 0; n < arguments.length; n++) {
        result += arguments[n];
      }
      this.result = result;
    }
    var ninja1 = {};
    var ninja2 = {};
    juggle.apply(ninja1,[1,2,3,4]);
    juggle.call(ninja2, 5,6,7,8);
    
    assert(ninja1.result === 10, "juggled via apply");
    assert(ninja2.result === 26, "juggled via call");
    

    5. 使用箭头函数绕过函数上下文

    箭头函数的this也就是函数上下文是从它的上层作用域继承来的

    this.click = () => {
      this.clicked = true;
    };
    

    6. 使用bind函数

    bind也是函数原型的一个方法, 为一个函数绑定它的上下文

    var boundFunction = button.click.bind(button);
    

    闭包和作用域

    什么是闭包

    闭包允许函数访问并操作函数外部的变量。只要变量或函数存在于
    声明函数时的作用域内,闭包即可使函数能够访问这些变量或函数

    var outerValue = "samurai";
    var later;
    function outerFunction() {
    var innerValue = "ninja";   
      function innerFunction() {
        assert(outerValue === "samurai", "I can see the samurai.");
        assert(innerValue === "ninja", "I can see the ninja.")
      }
      later = innerFunction;
    }
    outerFunction();   
    later();  
    

    当在外部函数中声明内部函数时, 不仅定义了函数的声明, 而且还创建了一个闭包。 该闭包不仅包含了函数的声明, 还包含了在函数声明时该作用域中的所有变量。 当最终执行内部函数时, 尽管声明时的作用域已经消失了, 但是通过闭包, 仍然能够访问到原始作用域

    使用闭包

    通过构造函数创建了一个含有两个方法getFeints,feint的对象,函数内部的feints变量只有通过getFeints实现了闭包才能访问,从而实现了封装私有变量

    封装私有变量

    function Ninja() {  
      var feints = 0;    
      this.getFeints = function() {
        return feints;
      };
      this.feint = function() {
        feints++;
      };
    }
    var ninja1 = new Ninja();  
    ninja1.feint();  
    assert(ninja1.feints === undefined,  
    "And the private data is inaccessible to us.");
    assert(ninja1.getFeints() === 1,
    "We're able to access the internal feint count.");  
    var ninja2 = new Ninja();
    assert(ninja2.getFeints() === 0,
    "The second ninja object gets its own feints variable.");
    

    回调函数

    处理回调函数是另一种常见的使用闭包的情景。 回调函数指的是需
    要在将来不确定的某一时刻异步调用的函数。 通常,在这种回调函数
    中, 我们经常需要频繁地访问外部数据。

    function animateIt(elementId) {
      var elem = document.getElementById(elementId);  
      var tick = 0;  
      var timer = setInterval(function() {
        if (tick < 100) {
          elem.style.left = elem.style.top = tick + "px";
          tick++;
        }
        else {
          clearInterval(timer);
          assert(tick === 100,
          "Tick accessed via a closure.");
          assert(elem,
          "Element also accessed via a closure.");
          assert(timer,
          "Timer reference also obtained via a closure.");
        }
      }, 10);
    }
    animateIt("box1");
    

    未来的函数: 生成器和promise

    使用生成器函数

    生成器函数几乎是一个完全崭新的函数类型, 它和标准的普通函数
    完全不同。生成器(generator) 函数能生成一组值的序列, 但每个值的生成是基于每次请求, 并不同于标准函数那样立即生成。 我们必须显式地向生成器请求一个新的值, 随后生成器要么响应一个新生成的值, 要么就告诉我们它之后都不会再生成新值。 更让人好奇的是, 每当生成器函数生成了一个值, 它都不会像普通函数一样停止执行。 相反, 生成器几乎从不挂起。 随后, 当对另一个值的请求到来后, 生成器就会从上次离开的位置恢复执行。

    function* WeaponGenerator() {  
      yield "Katana";
      yield "Wakizashi";
      yield "Kusarigama";  
    }
    for (let weapon of WeaponGenerator()) {
      assert(weapon !== undefined, weapon);
    }
    

    通过迭代器对象控制生成器

    调用生成器函数不一定会执行生成器函数体。 通过创建迭代器对
    象, 可以与生成器通信。

    function* WeaponGenerator() {
      yield "Katana";
      yield "Wakizashi";
    }
    const weaponsIterator = WeaponGenerator();
    const result1 = weaponsIterator.next();  
    assert(typeof result1 === "object"
          && result1.value === "Katana"
          && !result1.done,
    "Katana received!");
    const result2 = weaponsIterator.next();
    assert(typeof result2 === "object"
          && result2.value === "Wakizashi"
          && !result2.done,
    "Wakizashi received!");  
    const result3 = weaponsIterator.next();
    assert(typeof result3 === "object"
          && result3.value === undefined
          && result3.done,
    "There are no more results!");
    

    通过调用生成器得到的迭代器, 暴露出一个next方法能让我们向生
    成器请求一个新值。 next方法返回一个携带着生成值的对象, 而该对象中包含的另一个属性done也向我们指示了生成器是否还会追加生成值。

    function* WeaponGenerator(){
      yield "Katana";
      yield "Wakizashi";
    }
    const weaponsIterator = WeaponGenerator();  
    let item;
    while(!(item = weaponsIterator.next()).done) {
      assert(item !== null, item.value);
    }
    

    把执行权交给下一个生成器

    function* WarriorGenerator(){
      yield "Sun Tzu";
      yield* NinjaGenerator();  
      yield "Genghis Khan";
    }
    function* NinjaGenerator(){
      yield "Hattori";
      yield "Yoshi";
    }
    for(let warrior of WarriorGenerator()){
      assert(warrior !== null, warrior);
    }
    

    作为生成器函数参数发送值

    function* NinjaGenerator(action) {
      const imposter = yield ("Hattori " + action);  
      assert(imposter === "Hanzo",
        "The generator has been infiltrated");
      yield ("Yoshi (" + imposter + ") " + action);
    }
    const ninjaIterator = NinjaGenerator("skulk");  
    const result1 = ninjaIterator.next();
    assert(result1.value === "Hattori skulk","Hattori   is skulking");  
    const result2 = ninjaIterator.next("Hanzo");
    assert(result2.value === "Yoshi (Hanzo) skulk",
      "We have an imposter!");  
    

    生成器内部构成

    • 挂起开始——创建了一个生成器后, 它最先以这种状态开始。 其中
      的任何代码都未执行。
    • 执行——生成器中的代码执行的状态。 执行要么是刚开始, 要么是
      从上次挂起的时候继续的。 当生成器对应的迭代器调用了next方
      法, 并且当前存在可执行的代码时, 生成器都会转移到这个状态。
    • 挂起让渡——当生成器在执行过程中遇到了一个yield表达式, 它会
      创建一个包含着返回值的新对象, 随后再挂起执行。 生成器在这个
      状态暂停并等待继续执行。
    • 完成——在生成器执行期间, 如果代码执行到return语句或者全部代码执行完毕, 生成器就进入该状态

    使用promise

    promise是ES6中引入来解决异步人物的一个方案,promise对象是对我们现在尚未得到但将来会得到值的占位符;它是对我们最终能够得知异步计算结果的一种保证。

    const ninjaPromise = new Promise((resolve, reject) => {
      resolve("Hattori");
      //reject("An error resolving a promise!");
    });
    ninjaPromise.then(ninja => {
      assert(ninja === "Hattori", "We were promised Hattori!");
      },err => {  
      fail("There shouldn't be an error")
    });
    

    resolve, reject

    resolve为异步任务成功后调用下一个then

    const ninjaImmediatePromise = new Promise((resolve, reject) => {
      report("ninjaImmediatePromise executor. Immediate resolve.");
      resolve("Yoshi");
    });
    ninjaImmediatePromise.then(ninja => {
    assert(ninja === "Yoshi",
    "ninjaImmediatePromise resolve handled with Yoshi");
    });
    

    reject为失败抛出异常,由catch接住

    const promise = new Promise((resolve, reject) => {
      reject("Explicitly reject a promise!");  
    });
    promise.then(() => fail("Happy path, won't be called!"), error => pass("A promise was explicitly rejected!")  
    );
    

    把生成器和promise相结合

    将生成器和promise结合, 从而以优雅的同步代码方式完成异步任务

    async(function*(){  
      try {
        const ninjas = yield getJSON("data/ninjas.json");
        const missions = yield getJSON(ninjas[0].missionsUrl);
        const missionDescription = yield getJSON(missions[0].detailsUrl);
      }
      catch(e) {
      }  
    });
    function async(generator) {  
      var iterator = generator();  
      function handle(iteratorResult) {  
        if(iteratorResult.done) { return; }  
        const iteratorValue = iteratorResult.value;
        if(iteratorValue instanceof Promise) {
          iteratorValue.then(res => handle(iterator.next(res)))
        .catch(err => iterator.throw(err));
        }
      }
    
      try {
        handle(iterator.next());
      }
      catch (e) { iterator.throw(e); }  
    }
    

    async函数获取了一个生成器, 调用它并创建了一个迭代器用来恢
    复生成器的执行。 在async函数内, 我们声明了一个handle函数用于处理从生成器中返回的值——迭代器的一次“迭代”。 如果生成器的结果是一个被成功兑现的承诺,我们就是用迭代器的next方法把承诺的值返回给生成器并恢复执行。如果出现错误, 承诺被违背, 我们就使用迭代器的throw方法(告诉过你迟早能派上用场了) 抛出一个异常。 直到生成器的工作完成前, 我们都会一直重复这几个操作

    实现同样功能的async函数

    通过在关键字function之前使用关键字async, 可以表明当前的函数依赖一个异步返回的值。 在每个调用异步任务的位置上, 都要放置一个await关键字, 用来告诉JavaScript引擎, 请在不阻塞应用执行的情况下在这个位置上等待执行结果。

    (async function () {
      try {
        const ninjas = await getJSON("data/ninjas.json");
        const missions = await getJSON(missions[0].missionsUrl);
        console.log(missions);
        }c
        atch(e){
        console.log("Error: ", e);
      }
    })()
    
  • 相关阅读:
    (笔试题)机器人的运动范围
    (排序)快速排序QuickSort
    (笔试题)风口的猪-中国牛市
    (笔试题)小米Git
    同一片蓝天下,有些人以你想象不到的方式活着
    为什么那么多人工作都不开心?
    比你优秀的人都在努力
    海马体记忆训练:让你拥有超常记忆力
    致青春:不虚度,是对青春最好的交代
    你的袜子还是干的吗?
  • 原文地址:https://www.cnblogs.com/secoding/p/11160908.html
Copyright © 2020-2023  润新知