UP | HOME

secrets-of-the-javascript-ninja-2nd

Table of Contents

Chapter 1: JavaScript is everywhere

Understanding the JavaScript language

  • 很多的js coder都会在使用js的时候感到熟悉,因为他们会用到很多来自其他语言的 特性
  • 然而除了一些很基本的情况以外,这些'相同感'却并不一定正确,不能因为js从C-like language那里借鉴了很多东西(比如关键字),就说js和他们在机制上也有相同点
  • 理解了java就理解了js是一种错觉,和其他语言相比,js拥有更多的functional语言的 特性:
    • Function是first-class object:function是js最核心的概念, 在js中function和object 拥有完全一样的能力,外加还能被call
    • Function closure: function closure也是js最核心的概念,而且同时还被人民广泛 的误解着.简单的理解就是,function本身就是一个closure,内部被使用的外部变量都 在这个closure里面被维护着
    • Scope: 知道ES5, js都没有block-level的variable,所以我们要依靠global variable 和function level variable
    • Prototype-based object orientation: 和其他的主流编程语言不同(它们主要使用 class-based object orientation), js里面使用的是prototype.
  • js里面有两组紧密联系的概念, 理解这两组概念,有利于加深我们对js的理解
    • object和prototype
    • function和closure
  • 除了这些概念,一些js的feature可以帮助你写出更elegant和efficient的代码.这些feature 有:
    • generators: 就是一个function,但是会generate多个value
    • promises: 能够更好的控制asynchronous代码
    • proxies:能够让我控制一些object
    • Advanced array methods: 能够让array的处理更加优雅
    • Map和set: 常用的数据结构
    • Regular expression: 能让我们代码更简单
    • Modules: 让代码变得更小,更容易维护

How will JavaScript evolve?

  • ECMAScript委员会负责js的标准工作,并且已经提出了ES7/ES2016的标准
  • 这次升级比起ES6来说并不多,因为以后js每年都会升级
  • yearly的升级很令人振奋,但是并不意味着web开发者能够马上用到这些特性,我们可 以通过下面的链接来跟踪各大浏览器对最新规范的支持 https://kangax.github.io/compat-table/es6/

Transpilers give us access to tomorrow's JavaScript today

  • 即便浏览器更新的再快,我们也不能要求所有的用户马上开始使用最新版本的浏览器, 还是会有老的浏览器在被使用.
  • 为了让我们包含最新feature的代码能够在所有老的浏览器里面也能跑,我们的办法是 使用transpiler把包含最新feature的代码,转换成大部分浏览器支持的代码.尽可能 保证代码的功效一致

Understanding the brower

  • 今天,js可以从多个平台进行运行(指的nodejs已经诞生),但是这一切的根源还是来自 于浏览器
  • 对于浏览器来说,主要的三个重点是:
    • Document Object Model (DOM): 所谓DOM,是client-side web application UI的一 种'结构化的表达方式'.理解DOM对于能够写好js非常重要
    • Events: 大部分的JS application都是event-driven的application,这就意味着大 部分的代码被运行,其实是为了响应某些event,比如click,mouse move, keyboard press. 而其中的timer则是更让人难以理解的部分
    • Browser API: 为了帮助我们和world进行交流,browser提供了一个API,让我们来了解 设备信息,存储local data,和远程的server通信
  • 但是我们首先面对的,却是浏览器的各种不方便,因为浏览器总是存在各种bug,而且对API 的支持也不充分

Using current best practices

  • 掌握js语言,并且理解跨浏览器的coding issue罪域我们变成web 开发高手非常重要,但 是除了这两者以外,前人的,被经验证明好的一些实践也很重要.这就是我们说的best practice, 包括:
    • Debuggin skill
    • Testing
    • Performance analysis

Debugging

  • 曾经对于js来说, 调试就是使用alter来打印变量,但是今天已经有很多的浏览器都有 内置的developer工具了
  • 大部分的浏览器都内置了debug工具,这些工具的功能相同,都来源于Firebug的创意, 包括如下的几个方面:
    • 对DOM的探索
    • debug js
    • 编辑Css
    • 跟踪network

Testing

  • 本书采用的是assert(condition, message)的方式来测试是否结果如愿,比如测试a是 否为1
    assert(a === 1, "Disaster! a is not 1!");
    

Performance analysis

  • JS engine已经为performance做了很多努力,但是我们还要在自己的代码中测试自己 的性能,我们代码的主要逻辑如下
    <html>
        <head>
            <script>
             console.time("my start");
             for (var n = 0;  n < 12345; n++) {
                 console.log('log');
             }
             console.timeEnd('end');
            </script>
        </head>
        <body>
        </body>
    </html>
    

Chapter 2: Building the page at runtime

  • 我们对js的探索主要是在client-side的context下进行的,并且把browser作为执行的engine
  • 为了让我们的探索更加容易,我们首先要理解完整的web application lifecycle,特别 是如何把js嵌入到这个lifecycle里面
  • 我们从page开始被request开始来查看client-side web application的lifecycle.在接 下来的过程中,用户会有交互,直到page被关闭
  • 开始的时候是HTML代码的执行,然后关注js code,最后就是event handle
  • 在这个过程中,我们会探索一些概念,比如DOM,或者event loop(决定event如何被application 处理)

2.1 The lifecycle overview

  • client-side的lifecycle开始于我们在浏览器里面打入url,比如google.com
  • 浏览器会为用户服务,把刚才的url转换成一股request,并把这个request发给server
  • server会处理这个request,然后把这个request转换成一个response返回给server, 这 个response里面往往就包含HTML, CSS, JS等
  • 浏览器接收到response的时候,就是我们client-side web app真正开始它的life的时候
  • 我们的client-side web application(js代码和浏览器一起)是一个GUI程序,GUI程序 无论是我们web的,还是desktop的,都遵守一些相似的phase,比如如下两个步骤:
    • page building: 设置user interface
    • event handling: 进入一个loop然后等待event来临,然后event来临以后就触发event handler
  • event handling就是一个循环了,只有用户关闭这个页面才会结束这个循环
  • 下面我们看一个例子
    <!DOCTYPE html>
    <html>
        <head>
            <title> Web app lifecycle</title>
            <style>
             #first { color: green; }
             #second { color: red; }
            </style>
        </head>
        <body>
            <ul id="first"></ul>
            <script>
             function addMessage(element, message) {
                 var messageElement = document.createElement("li");
                 messageElement.textContent = message;
                 element.appendChild(messageElement);
             }
             var first = document.getElementById("first");
             addMessage(first, "Page loading");
            </script>
    
            <ul id ="second"></ul>
            <script>
             document.body.addEventListener("mousemove", function(){
                 var second = document.getElementById("second");
                 addMessage(second, "Event: mousemove");
             });
    
             document.body.addEventListener("click", function(){
                 var second = document.getElementById("second");
                 addMessage(second, "Event: click");
             });
            </script>
        </body>
    </html>
    
  • 这个例子主要有两个部分:
    • 创建一个first <ul> element,然后使用js加载一个<ul>的<li> element,写上'page loading', 这代表了page building的过程
    • 创建一个second <ul> element,然后使用js在document.body里面增加了两个event listener: 一个是mouse move一个是mouse click. 用户一旦有相应的鼠标动作,则 最后会打印在页面上.这个代表了event handling的过程

The page-building phase

  • 在一个web application可以被展示(进而操作)之前, 一个page必须使用server传过来 的response(包括html, css, js)来组合成自己的页面.这个phase的目标就是建设好web application的UI, 而这个phase又分成了两个step:
    1. 分析HTML,并且建设好Document Object Model(DOM)
    2. 执行JS 代码
  • 简单点说,step1就是分析html element代码(除了script的部分),而step2就是分析script 的部分.这两个部分的分析可以交替进行,并不是严格的一个接着一个

Parsing the HTML and building the DOM

  • page-building phase是从接到html代码开始的, 浏览器根据对html代码的分析建立起 一个DOM结构,所谓DOM就是对HTML的一种structured的表达方式
  • DOM通常会被叫做DOM tree,因为它确实是一种树形结构的,比如我们上面代码在遇到 <script> tag之前就会建设成如下的一种DOM tree
                                +--------+
                      +---------+  html  +----------+
                      |         +--------+          |
                      |                             |
                      |                             |
                +-----+--+                      +---+----+
         +------+  head  +---+               +--+  body  +---+
         |      +--------+   |               |  +--------+   |
         |                   |               |               |
    +----+---+         +-----+--+         +--+-----+   +-----+--+
    | title  |         | style  |         |   ul   |   | script |
    +--------+         +--------+         +--------+   +--------+
    
  • 需要注意的是html和DOM不是一回事,html是DOM的蓝图,DOM是根据html来创建的,而且 浏览器有时候还会修正html里面的一些错误.比如下面的例子,<p>写在了<head>里面 其实<p>是要在body里面才会有意义,DOM创建的时候就会自动修正
    <html>
      <head>
        <p>
          Hello
        </p>
      </head>
      <body>
      </body>
    </html>
    
  • 当DOM碰到特殊的tag的时候,比如<script>它会停下自己的脚步,转换为另外一个step, 也就是js代码执行阶段(当然了两个step是交替执行的,一会还会回来)

Executing JavaScript Code

  • 所有html里面的script element都是由浏览器的javascript engine来执行的,这些引 擎最出名的就是google的V8了,同时还有firefox的Spidermonkey
  • js和浏览器交互的方法是: 浏览器通过'global object'提供了一些API,让js来和page 进行互动
GLOBAL OBJECTS IN JAVASCRIPT
  • 浏览器expose给js引擎最重要的global object是window object,这个object代表了 包含当前page的窗口!
  • 这个window object有很多properties,每个property都是对我们非常重要的.换句话 说, window的最主要作用就是包裹了一系列properties给我们用
  • 被包裹的一系列properties最重要的是document,也就是DOM的js表示.
  • 这里插入介绍一个重要的概念的解读,就是
    JavaScript that runs in the browser has Window as it's top level
    
  • 换句话说,定义在浏览器js里面的global variable就会变成window的properties,而 windows在设计的时候的哪些'有用的property'同时也会变成我们js代码的global variable.
    window.document === document    // true
    
  • 这也为什么我们可以直接在html里面使用document,而不需要window.document
    var first = document.getElementById("first");
    
  • 我们得到了这个first的变量,其实就是一个object啦,它指代的就是id为'first'的 html element. 我们可以通过代码来给这个element增加一个child,或者更改这个 element本身.这个例子是为这个element增加了一个<li>的element作为child
    var messageElement = document.createElement("li");
    element.appendChild(messageElement);
    
DIFFERENT TYPES OF JAVASCRIPT CODE
  • 在广义上,运行在浏览器里面的js代码会被分成两类:
    • global code: 会被浏览器js engine'自动'调用运行
    • function code: 定义逻辑,不会被'自动'运行
EXECUTING JAVASCRIPT CODE IN THE PAGE-BUILDING PHASE
  • 当浏览器遇到script tag的时候,它只能放下自己的DOM创建工作,腾出手来处理<script> tag带来的js code
  • 在这个例子里面就是第一个<script> tag,在这个tag里面会为id为'first'的<ul>创 建一个<li> tag
  • 通过document.getElementById找到某个element以后,理论上我们可以对"DOM做任何 事情",但也不是绝对,比如我们就没法对id为"second"的element做什么,因为在第一 个<script> tag里面还没有创建"second" element.这也是为什么很多人选择把js的 代码放在最后的原因
  • 执行完<script>tag,我们的代码就会继续自己的'根据html element来创建DOM'的工 作,然后遇到<script>tag在来依靠js engine工作,循环往复这两个过程,直到所有的 html代码都处理完毕
  • 需要注意的是,因为window这个top level的存在,所以在某个<script>里面创建的全 局变量,在其他的<script> tag里面都继续可用

Event handling phase

  • client-side web application其实也是一种GUI: 它也是处理用户的点击,键盘敲动, 鼠标移动等操作
  • 所以我们在page-building phase阶段的js代码除了可以'帮助创建DOM tree'以外,还 可以register event listener.所谓event listener就是
    Functions that are executed by the browser when an event occurs.
    

Event-handling overview

  • browser运行的核心model是single-threaded execution model,也就是说浏览器内部 最多只有一个thread在运行,那么也只有一段代码可以"占据"浏览器而运行
  • single-threaded execution model的特点是需要一个queue来存储所有的发生的event 原因很简单,event的发生是快速的,异步的.不过不记录下的话,你不能保证browser都 能记住,使用queue的原因是因为这个是一个明显的"先到先服务"的例子
  • 所有的event(包括user-generated,比如点击页面,和server-generated,比如Ajax event) 都会放到同一个queue里面,顺序是browser发现他们的顺序
  • queue的处理顺序是我们很熟悉的了:
    • browser会去queue里面找head的event
    • 如果queue里面没有event,那么就keep checking
    • 在处理head event的时候,queue里面的其他的event要耐心的等待
  • 因为event handle是一次处理一个的,所以我们非常的小心handle event的时间不要 太长
  • 还需要注意的是,我们是调用的windows的API来添加的event listener,但是如下两个 方面不在我们本书的讨论范围:
    • 具体browser如何把event放到自己的queue里面
    • browser如何发现的有新的event发生
EVENTS ARE ASYNCHRONOUS
  • event的发生是没法预料的,顺序也是没有规则的.所以我们说对event的handling(以 及对于handling function的调用)都是"异步的(asynchronous)"
  • event可能是下面的任何一种type:
    • Browser event: 比如'page load完成'就是一种event
    • Network event: 比如server发送来response
    • User event: 比如mouse clicks, mouse move, key press等等
    • Timer event: 比如当一个timeout到期了
  • 除了global code 以外,我们放到<script>里面的绝大部分的代码都是"某种event发 生"后才会去执行的
  • 哪些代码会"钟意"哪些event是由我们的注册机制决定的

Registering event handlers

  • 在client-side web application里面,有两种方式来设置event:
    • 把某个function设置成object'特殊的'几个property,比如onload. 注意,所有window 下面的任意的object的onload如果被设置成了某个function都会注册相应的handler
      window.onload = function() {}
      document.body.onload = function() {}
      
    • 使用内置的addEventListener函数
      document.body.addEventListener("mousemove", function(){});
      
  • 这里两种方法,我们更推荐第二种,因为第二种是'add',也就是在现有的event handler 里面为某个event'再加一个event', 而第一种是'replace',会覆盖掉原来的event handler

Handling events

  • event handling的方式很简单,浏览器发现了新的event以后,就调用相应的handler function,需要注意的是只有某个function fully 返回以后,下一个handler function 才有机会
  • 所以我们要注意,我们的event handler的时间不要太长,否则client-side web app会 看起来没有响应

Chapter 3: First-class functions fro novice: definitions and arguments

  • 不太了解js的人肯定有些诧异,我们最开始介绍的不是object,而是function. 懂得人其 实应该知道,因为javascript不是一个object-oriented language,而是一个functional language !
  • js里面的function被称之为first-class object是因为:
    • 首先object可以做的(被variable refer, literal式的声明, pass as 函数参数)function 都可以做.
    • 其次function还有比object高明的地方:它可以被调用

What's with the functional difference?

  • functional的思想非常重要的原因在于function是JS里面执行的'主要的执行小单元': 所谓的'主要的执行小单元'是因为除了global的在page-building阶段就会被执行的代 码外,绝大部分的js代码都'以function的形式执行的'
  • 我们已经说了function是object的超集(object的能力,function都具备),下面就列举 一下js下面object的能力:
    • 可以使用literal的方式创建
      {}; // an object
      
    • 可以被赋值给variable, array, 或者其他object的properties
      var ninja = {};
      ninjaArray.push({});
      ninja.data = {};
      
    • 可以作为参数传递给function
      function hide(ninja) {
          ninja.visibility = false;
      }
      hide({});
      
    • 可以被function返回
      function returnNinja() {
          return {};
      }
      
    • object可以动态创建,然后'再'赋于它property
      var ninja = {};
      ninja.name = "Hanzo";
      

Functions as first-class objects

  • function拥有object前面所述的所有能力:
    • 通过literal创建
      function ninjaFunction() {}
      
    • 赋值给变量,加入数组,或者作为其他object的property
      var ninjaFunction = function() {};
      ninjaArray.push(function(){});
      ninja.data = function(){};
      
    • 作为其他function的参数
      function call(ninjaFunction) {
          ninjaFunction();
      }
      // use it
      call(function(){});
      
    • 作为function的返回值
      function returnNewNinjaFunction() {
          return function(){};
      }
      
    • 动态创建,'再'加property
      var ninjaFunction = function(){};
      ninjaFunction.name = "Hanzo";   // add a new property to a function
      
  • 在这么多的特性中,有个特性更接近特殊,那就是我们可以把function作为参数传递给 另外一个function,而另外一个参数在自己内部调用'参数function',我们把这个叫做 callback function

Callback functions

  • 每当我们设置一个function,然后这个function在一个later time被调用,那么我们就 把这种情况叫做callback
  • 我们前面为event设置listener, 然后浏览器自动为event帮我们调用event handler(其 实就是一个hans)的情形其实就是callback
  • 我们可以自己实践callback,就是把一个function作为参数传递给另外一个function
  • 我们先来看一个callback的例子
    var text = "Demo !";
    
    function useless(ninjaCallback) {
        console.log("In useless function");
        return ninjaCallback();
    }
    
    function getText() {
        console.log("In getText function");
        return text;
    }
    
    console.log(useless(getText));
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // In useless function                            //
    // In getText function                            //
    // Demo !                                         //
    ////////////////////////////////////////////////////
    
  • 这里的useless就是一个callback(当然了,有些人认为马上调用'传入function参数' 的做法不叫callback,我们这里只是以这个为例子),它在自己的内容调用了'传入的 function参数'作为返回值
  • js最重要的一个feature就是能够在任何'expression可以出现的地方'创建一个function 这样做有两个好处:
    • 让代码compact,而且易懂
    • 因为这个function只在这里使用,我们完全可以不用给这个function取名字,这样就 不会污染global namespace啦!
  • 重写后代码如下
    var text = "Demo !";
    
    function useless(ninjaCallback) {
        console.log("In useles function");
        return ninjaCallback();
    }
    
    console.log(useless(function(){
        console.log("In parameter");
        return text;
    }));
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // In useles function                             //
    // In parameter                                   //
    // Demo !                                         //
    ////////////////////////////////////////////////////
    
  • 前面我们为event增加event listener的例子,也是一个callback的例子,在那个例子 中,我们的callback函数,也是匿名的
    document.body.addEventListener("mousemove", function() {
        var second = document.getElementById("second");
        addMessage(second, "Event: mousemove");
    });
    
SORTING WITH A COMPARATOR
  • 说起使用function作为参数这件事情,在js里面有个特别好的例子,就是排序数组.我 们知道在java里面,你要给数组一个comparator来决定其顺序
  • 在js里面,我们只需要一个函数.这个函数设计也很新颖,参数是数组的两个成员:
    • 两个参数比较,如果返回负数,说明他俩位置是对的.
    • 如果返回正数,说明位置要swap
    • 如果返回0,说明他俩相等
  • 代码如下
    var values = [0, 3, 2, 5, 7, 4, 8, 1];
    
    console.log(values.sort(function(value1, value2) {
        return value1 - value2;
    }));
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // [ 0, 1, 2, 3, 4, 5, 7, 8 ]                     //
    ////////////////////////////////////////////////////
    

Fun with functions as objects

  • 一个可能令人吃惊的事情是:我们完全可以给function赋予一个property,因为object 可以有property,那么function就必然可以有property
  • 这个特性,可以让js做下面的两种hack

Storing functions

  • 这个hack看起来很厉害,其实就是给function一个identification而已,防止重复加载 一个function到一个列表(collection)里面
  • 这个特性在维护event的call back列表的时候非常有用: 我们希望我们的event的call back 函数礼包里面所有的函数只出现了一次.
  • 面对'唯一性'的问题,set(或者hash)是一个非常简单的思路.其实这里就是利用了function 可以多加一个域(property),把这个域作为set或者hash里面的key而已(同时下例中的 cache就是这个hash(set)
    const assert = require('assert');
    var store = {
        nextId: 1,
        cache: {},
        add: function(fn) {
            if (!fn.id) {
                fn.id = this.nextId++;
                this.cache[fn.id] = fn;
                return true;
            }
        }
    };
    
    function ninja(){}
    
    console.log(store.add(ninja));
    console.log(store.add(ninja));
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // true                                           //
    // undefined                                      //
    ////////////////////////////////////////////////////
    

Self-memoizing function

  • 既然函数内部可以存储property,那么我们可以考虑在function内部记录已经计算过 的值,比如下面计算prime number的例子,每次计算完一个参数我们把值记录下来,下 次再有相同的计算我们之间返回就是了
    function isPrime(value) {
        if (!isPrime.answers) {
            isPrime.answers = {};
        }
    
        if (isPrime.answers[value] !== undefined) {
            console.log("Cached value returned!");
            return isPrime.answers[value];
        }
    
        var prime = value !== 1; // 1 is not a prime
    
        for (var i = 2; i < value / 2; i++) {
            if (value % i === 0) {
                prime = false;
                break;
            }
        }
        return isPrime.answers[value] = prime;
    }
    
    console.log(isPrime(5));
    console.log(isPrime(5));
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // true                                           //
    // Cached value returned!                         //
    // true                                           //
    ////////////////////////////////////////////////////
    

Defining functions

  • js提供了如下几种定义function的方法:
    • function declaration 和 function expression: 名字和长相很像,但确实是两种 不同的定义方法
      function myFun() { return 1;} // function declaration
      var myFun2 = function() { return 2; } // function expression
      
    • arrow function: es6提供的新的函数定义方式
      var myFun3 = (x) => x + 1;
      
    • function constructor: 比较偏门的方法,我们不予讨论
      new Function('a', 'b', 'return a + b');
      
    • generator function: 也是ES6新加的,和普通函数不同的是,它可以exited然后后面 reentered application execution,同时保存他们的variable
      function* myGen() { yield 1; }
      

Function declarations and function expressions:

  • 这两种的确很像,但是也有区别:
    • function declaration的名字是"必须要的", 因为否则你无法调用这个函数. 因为 function declaration必须自己是一个statement
    • function expression的名字却"不是必须要的",因它是一个expression,是一个statement 的一部分,它总是会把:
      1. 自己放到一个变量里面.
        var doNothing = function() {};
        doNothing();
        
      2. 或者赋予一个参数, 其实也是变相的赋值给参数:
        function doSomething(action) {
            action();
        }
        
IMMEDIATE FUNCTIONS
  • 因为function expression嘛,它也可以是一个call statement的"调用者",如下
    console.log((function(x){ return x *x; })(6));
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 36                                             //
    ////////////////////////////////////////////////////
    
  • 前面的例子在function expression的左右使用了'()'来告诉js engine,我们面对的 不是statement,而是expression.还有更难懂的方法,就是在前面使用一个unary operator 作用也是一样,告诉js engine,我们处理的是function expression,不是statement. 而unary operator处理的结果就被直接丢弃了,我们不需要了.在很多library里面都 有这种奇怪的用法

Arrow functions

  • 因为js里面function用到的地方太多,es6专门为function设计了一种新的格式,其格式为
    paramter => expression
    
  • expression的值会直接作为返回值返回,ruby用户会非常熟悉
    values.sort((value1, value2) => value1 - value2);
    
  • 但是如果有多个statement的情况下,还是需要一个return的,而且这个时候要在=> 后 面加上一个{}包裹这些statement. 如果忘记return的话,这个function返回值为undefined
    var greet = name => {
        var helloString = 'Greetings ';
        return helloString + name;
    };
    

Arguments and function parameters

  • 当讨论function的时候,我们会讨论两个概念:
    • parameter: 是function 定义的时候概念
    • argument: 是function调用的时候的概念
  • js中,argument和paramter的个数不要求对应(不会去检查):
    • 如果argument的个数少于parameter,那么'从左到右'进行赋值.没有得到赋值的parameter 就会被设置为undefiend
    • 如果argument的个数多余parameter,剩下的依然可以使用关键字arguments访问TODO

Rest parameters

  • rest parameter也是ES6新加入的特性
  • rest parameter有点类似c语言里面的'参数个数不定'的情况(golang里面也有相似的 设置),就是有些情况下,我们肯定会有'至少一个参数',但是从第二个参数到最后一个 参数具体有多少个不定.而我们可以把这些(从2到最后)个参数设置成一个数组
    var multiMax = (first, ...remaining) => first * remaining.sort()[2];
    
    console.log(multiMax(3, 1, 2, 3));
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 9                                              //
    ////////////////////////////////////////////////////
    

Default parameters

  • 这就是其他语言早就有的特性了
    var performAction = (ninja, action="skulking") =>
        ninja + " " + action;
    
    console.log(performAction("Fuma"));
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Fuma skulking                                  //
    ////////////////////////////////////////////////////
    

Chapter 4: Functions for the journeyman: understanding function

  • 本章我们主要讨论如下的两个implicit function parameter,说是implicit parameter 是因为:即便你在function调用的时候,没有传入这两个参数,它们依然可以被当做传入 了一样被使用:
    • this: 指代了当前function"所属"的object,是js一种特殊的靠近'面向对象编程'的特性
    • arguments: 指代传入的所有参数的集合,是一种'类似'数组的东西

Using implicit function parameters

  • implicit parameter是'默默'的传入,但是使用起来和explicit paramter 没有区别

The arguments paramter

  • arguments能够获取到传入的所有的argument(无论他们和paramter对应的好不好)
  • 在rest parameter引入了以后,arguments的作用就没那么大了,但是在处理legacy code的时候还是会遇到,我们要了解它
  • arguments在两个方面很像array:
    • 它有一个property叫做length
    • 它可以使用operator[]来访问
  • 但是我们极力避免把arguments叫做array, 因为它的确就不是array. 这一点容易被 人们忽略,我们要谨记不要把array的feature使用在arguments上面
  • 这也是为什么rest paramter更好的原因,因为rest parameter的最后一个参数的的确 确是array
ARGUMENTS OBJECT AS AN ALIAS TO FUNCTION PARAMETERS
  • 换句话说就是, arguments和paramter是一种'硬链接'的关系:
    • 一旦对argument进行更改, 第一个参数也会随之更改
    • 一旦对第n个参数更改, argument[n-1]也会随之更改
AVOIDING ALIASES
  • arguments的这种alias关系是不好的.所以我们要禁止使用
  • 由于老的js中存在这很多这种不是特别好的特性,我们发明了一种禁止'一类js不好 的特性'的方法: strict mode
  • strict mode很简单,只要在js代码最上面协商'strict mode'就可以了,这等于告诉 js engine:我们希望安装strict mode 来执行代码:
    • 不使用strict mode
      function infiltrate(person) {
          console.log(person);
          arguments[0] = 'ninja';
          console.log(person);
      }
      
      infiltrate("gradener");
      
      ////////////////////////////////////////////////////
      // <===================OUTPUT===================> //
      // gradener                                       //
      // ninja                                          //
      ////////////////////////////////////////////////////
      
    • 使用strict mode
      "use strict";
      
      function infiltrate(person) {
          console.log(person);
          arguments[0] = 'ninja';
          console.log(person);
      }
      
      infiltrate("gradener");
      
      ////////////////////////////////////////////////////
      // <===================OUTPUT===================> //
      // gradener                                       //
      // gradener                                       //
      ////////////////////////////////////////////////////
      

The this parameter: introducing the function context

  • this是一个很有意思的参数. 在objected-oriented语言中,一般都会有一个this来指 代当前的function是被那个object调用的.
  • js并不是一个oo语言,function的创建也并不一定要在object里面. 但是js为了有oo 特性,特意发明了this参数.并且为oo做了一些'妥协':
    • 如果我们的method有caller的话,那么this肯定就是指的caller object
    • 更多的情况下,我们并不会为一个function指定一个caller, 这种情况下又有两说:
      1. 老的js实现,也就是none-strict mode下面,我们的this会指向window,这是非常 不好的实现,因为我们调用function'没指定object',你却默认给我一个object, 这首先不合理.其实我们后面会看到,这种实现会'污染global namespace'
      2. 新的js为了修正老的js实现,引入了strict mode(前面的arguments的alias我们 已经看到过strict mode修正过一次js的不良设计了), 在strict mode里面.如 果调用function不加object的话,this会指向undefined
  • 下面我们来介绍下function有哪些被调用的方法

Invoking functions

  • 在js里面有如下几种调用function的方式:
    • As a function: 这是最直接的调用function的方式,比如skulk()
    • As a method: 这种方式会联系上一个object,为了oo设计而实现的, 比如ninja.skulk()
    • As a constructor: 使用new来调用function,比如new Ninja(), 是一种类oo的object 创建方法
    • 通过apply或者call调用,这种是把object'当做参数'的调用方式: 比如skulk.call(ninja)

Invocation as a function

  • 这么说的意思是function以最纯粹的方式调用:只使用operator()
  • 细细品味,有如下三种详细的调用方式:
    • function declaration, invoked as a function
      function ninja() {};
      ninja();
      
    • function expression invoked as a function
      var samurai = function(){};
      samurai();
      
    • immediately invoked function expression, invoked as a function
      (function(){})()
      
  • 以这种方式调用function,在function内部this会:
    • 在strict mode下面指向undefined
    • 在none strict mode下面指向window object

Invocation as a method

  • 当一个function被'设置'为一个object的property,然后当这个object调用这个function 的时候,我们就说function is invoked as a method of that object
    var ninja = {};
    ninja.skulk = function(){};
    ninja.skulk();
    
  • 这种情况下,this自然就指向了调用我们的object.这也是js极力模仿其他oo语言的地 方.
  • 这种情况下,function的this是不停变动的,根据调用method的object的不同,this会 会有各种不同
    function whatsMyContext() {
        return this;
    }
    
    // nodejs' [window] equivalent global variable [global]
    console.log(whatsMyContext() === global);
    
    var getMyThis = whatsMyContext;
    console.log(getMyThis() === global);
    
    var ninja1 = {
        getMyThis: whatsMyContext
    };
    
    console.log(ninja1.getMyThis() === ninja1);
    
    var ninja2 = {
        getMyThis: whatsMyContext
    };
    
    console.log(ninja2.getMyThis() === ninja2);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // true                                           //
    // true                                           //
    // true                                           //
    // true                                           //
    ////////////////////////////////////////////////////
    
  • 虽然我们的ninja1和ninja2都共享了一个function实体,但是其内部this的值确是不 一样的.
  • this的灵活指向,完成了js对oo语言的一种追求.但是这个例子中,这样实现ninja1和 ninja2其实是有点repeat代码的,也就是把两个object的getMyThis都指向whatsMyContext
  • oo语言中是可以通过继承来减少这种repeated代码的,我们后面会介绍

Invocation as a constructor

  • 总体上来说,任何一个function都可以作为一个constructor(可能arrow function有 些区别,后面会介绍),比如前面我们设置的whatsMyContext
    function whatsMyContext() { return this; }
    new whatsMyContext();
    
  • 但是这样做却是没有什么意义的,因为constructor function要做些事情,但是这里却 完全没做.只是说这样做编译器不会报错而已,并没有实现constructor的核心价值.要 实现ctor的核心价值,就得让ctor function和普通函数有点不同才行
THE SUPERPOWERS OF CONSTRUCTORS
  • 先看一个例子
    function Ninja() {
        this.skulk = function() {
            return this;
        };
    }
    
    var ninja1 = new Ninja();
    var ninja2 = new Ninja();
    
    console.log(ninja1.skulk() === ninja1);
    console.log(ninja2.skulk() === ninja2);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // true                                           //
    // true                                           //
    ////////////////////////////////////////////////////
    
  • 根据这个例子我们可以列出使用operator new xx()的方法来调用function的一些特 殊的地方:
    • 会首先创建一个empty object
    • 这个object会传递给ctor function,作为这个ctor function的this paramter,那 么也就成为了这cotr function的context
    • 这个ctor函数返回的时候,我们会把ctor function的this作为返回值返回,所以ctor function的结果可以返回给某个变量
  • 后面两点说明了为什么whatsMyContext不是一个好的ctor: 一个ctor是创建一个object, 然后再function内部对这个object进行配置,说白了就是对this进行配置,最终返回. whatsContxt都没有对this做过什么,自然就没有ctor的灵魂了
  • 我们一个标准的ctor应该如Ninja()
    function Ninja() {
        this.skulk = function() {
            return this;
        };
    }
    
CONSTRUCTOR RETURN VALUES
  • 其实ctor的设计,很明显是一种js这种functional语言"强行"适应面向对象编程语言 的一种妥协设计.所以最后出来了一种ctor function最后会返回'传递给它的新建的 object'的this指针的设计.
  • 所以问题来了,如果我们ctor function自己return了返回值怎么办?
  • 答案是, 看情况:
    • 如果返回的是nonobject value,比如1, 那么没事,everything 都OK
    • 如果返回的是object value, 那么ctor function的返回值就不再是那个新建的this 了,而是这个明确return的object.正是这种情况的存在,让我们必须要小心,同时 也使用各种办法让ctor function看起来更加不一样一点
CODING CONSIDERATIONS FOR CONSTRUCTORS
  • 我们的ctor function的价值在于我们对new创建的新object进行配置,所以一个有意义 的ctor function内部肯定会着重处理this
  • 但是如果我们调用的时候不小心忘了加new,那么我们ctor function里面对this的操作 都会是对window的操作!这会极大的污染global namespace.(在strict mode这不会发 生)
    // global equvivalent for window in nodejs
    console.log(Object.keys(global).includes('skulk'));
    
    function Ninja() {
        this.skulk = function() {
            return this;
        };
    }
    
    var whatever = Ninja();         // forget new !
    console.log(Object.keys(global).includes('skulk'));
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // false                                          //
    // true                                           //
    ////////////////////////////////////////////////////
    
  • 在strict mode诞生之前,我们能做出的努力(让ctor function看起来不一样一点)就 是naming convention了:
    • 普通函数的命名是首字母小写的驼峰模式,并且表述下自己做了什么,比如
      skulk, creep, sneak, doSomethingWonderful
      
    • ctor函数的命名是首字母大写的,一般是名词,比如
      Ninja, Samurai, Emperor, Ronin
      

Invocation with the apply and call methods

  • 到目前为止,我们看到的不同的function invocation类型的主要区别在于:implicit this 指向的function context是什么:
    • 对于method来说,this指向了method的object
    • 对于top-level来说,this指向window或者undfined
    • 对于ctor来说,this指向new新创建的这个object
  • 如果我们想随意指定一个object怎么办,比如top-level的function,我们不想使用window, 而想使用document来调用它们.
  • 为了解决这个问题,我们先来看一个'非常常见的'和event handling相关的bug.
  • 看这个bug之前,先来了解event handling的机制: 每当event handler被调用的时候, function context是被设置为event绑定的object
    When an event handler is called, the function context is set to
    the object to which the event was bound.
    
  • 我们的这个buggy的例子如下
    <button id ="test"> Click Me! </button>
    <script>
     function Button() {
         this.clicked = false;
         this.click = function() {
             this.clicked = true;
             assert(button.clicked, "The button has been clicked"); <!-- false -->
         };
     }
     var button = new Button();
     var elem = document.getElementById("test");
     elem.addEventListener("click", button.click);
    </script>
    
  • 这个例子的结果并不如人意, 每次调用都会显示'红色'的message,表示clicked的值是 false
  • 这个例子的问题在于button.click的调用,如果不和addEventListener掺和的话,那么 会把this默认为button object(这个是method的典型用法啊),而这里的问题是addEventListener 会把后面的callback的this改成elem(也就是addEventListener的调用者!),所以你对 this.clicked的操作,都会延续到elem上面!
  • 我们姑且还是认为这是js设计不合理的地方吧
  • 这个问题的根源在于我们没有'准确'函数的this object! 函数的this object如果能 够随心所欲的更改的话,那么我们就可以完成我们的工作了.
  • 幸运的是js确实是有这样的feature,那就是:
    • apply
    • call
  • apply和call其实是一回事,只不过apply要求第二个参数是数组,call则是把这个数组展开
    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);
    
    console.log(ninja1.result);
    console.log(ninja2.result);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 10                                             //
    // 26                                             //
    ////////////////////////////////////////////////////
    
  • 好了,你信心满满的把代码改成如下,希望能够得到'绿色'的结果
    elem.addEventListener.call(button, "click", button.click);
    
  • 结果浏览器却报错了,因为addEventListener是一个浏览器提供的API,它并不是一个真 正的js function
  • 要想真正的解决这个问题,我们要用到arrow function

Fixing the problem of function contexts

Using arrow functions to get around function contexts

  • 除了写起来简单以外,arrow function还有一个特性和原来的function与众不同,而且 更容易把它作为一个callback: arrow function的this不再是一个动态的值,它是不 能再次改变的(可以被认为是一个const, 无论call, apply还是bind都不能改变它), 它的初始化值是'创建时期enclosing scope'的this值
    Arrow functions don't have their own this value. Instead, they remember
    the value of the this parameter at the time of their definition.
    
  • 它的初始化值是'创建时期enclosing scope'的this值, 这句比较难以理解,我们可以 看看下面几个例子:
    • 在global创建的function,其this始终是指向window(global)的
      <script>
          var o = {
              traditionalFunc: function() {
                  assert(this === o, "traditionalFunc this === o?"); <!-- true -->
              },
              arrowFunc: () => {
                  assert(this === o, "arrowFunc this === o?"); <!-- false -->
                  assert(this === window, "arrowFunc this === window?"); <!-- true -->
              }
          };
      
          o.traditionalFunc();
          o.arrowFunc();
      </script>
      
    • 在ctor里面创建的,其this始终指向新创建的object,也就是我们后面的例子展现的.
  • 这样一来,刚才的问题就迎刃而解了.下面的例子中,所有的this都是一个this,就是button object
    function Button() {
      this.clicked = false;
      this.click = () => {
        this.clicked = true;
        assert(button.clicked, "The button has been clicked");
      }
    }
    var button = new Button();
    var elem = document.getElementById("test");
    elem.addEventListener("click", button.click);
    

Using the bind method

  • 我们再来回顾下前面的event handler出问题的那句代码
    elem.addEventListener("click", button.click);
    
  • 前面说addEventListener不是真正的js function,那么我们可以从button.click上面 来做做文章,依照我们现有的知识,我们可以这么做
    elem.addEventListener("click", button.click.call(button));
    
  • 这样做看起来可以,但是有一个问题:我们的apply或者call,是"调用一个函数",我们 的assert的确变成绿的了,但是只能绿一次,我们不能把这个function'再传递'给addEventListener
  • 所以我们很自然的想到,'再创建一个新的function,只不过this换掉的方案',那就是 bind了
  • 每个function都可以访问bind, bind会:
    • 创建一个新的函数并且作为返回值返回
    • 而且这个函数的this函数变成了bind的第一个参数
    • 这种bind是强制的,不会受到'如何调用'的影响(如果还受到addEventListener的影 响,那就没完没了了)
  • 下例中,我们使用bind来把button.click'绑定'在button上,然后返回就解决了这个问题
    var button = {
       clicked: false,
       click: function(){
         this.clicked = true;
         assert(button.clicked,"The button has been clicked"); <!-- true -->
       }
     };
     var elem = document.getElementById("test");
     elem.addEventListener("click", button.click.bind(button));
    
     var boundFunction = button.click.bind(button);
     assert(boundFunction !== button.click,
            "Calling bind creates a completyl new function"); <!-- true -->
    

Chapter 5: Functions for the master: closures and scopes

  • 和我们前面讲的function一样,closure也是最重要的javascript特性.虽然很多js开发 者可以在完全不理解closure带来的好处的基础上写出代码.但是我们还是要说,closure 的引进,为js增添了如下的好处:
    • 为一些高端的特性减少了代码的数量和复杂度: 和callback有关的代码,比如event handling,如果没有closure,就会非常的麻烦
    • 让我们使用一些如果没有closure根本不能实现的特性:比如private object variable
  • closure其实是functional programming language的特性,了解它有助于理解复杂的js 代码
  • closure其实是一种副作用(side effect),它是js scope工作原理的副作用
    Closures are a side effect of how scopes work in JavaScript.
    

Understanding closures

  • closure的出现,让js的function可以'读写'不在function内部的variable,当然这些个 variable必须是在function定义的scope里面(也就是说是function定义所在的那个{})
  • closure给function带来的好处,就是一个function可以在'以后任何的时间'被调用,甚 至是它定义的scope已经结束了
    var outerValue = "samurai";
    var later;
    
    function outerFunction() {
        var innerValue = "ninja";
    
        function innerFunction() {
            console.log('outerValue is ' + outerValue);
            console.log('innerValue is ' + innerValue);
        }
    
        later = innerFunction;
    }
    
    outerFunction();
    later();
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // outerValue is samurai                          //
    // innerValue is ninja                            //
    ////////////////////////////////////////////////////
    
  • 我们使用later变量来保存innerFunction,然后再后面去调用它,否则我们无法获取它 的名字(因为innerFunction定义在outerFunction里面)
  • later()调用的时候,outerFunction()已经运行完毕了,但是later()还是能看到outerVale 和innerValue,其中原因就是closure
  • 当我们在outer function里面定义innerFunction的时候,不知function declaration 定义了.同时一个closure也会被定义, 这个closure里面包括, function 定义时候的 所有的variable
    +----------------------------------------------------+
    |        +------------------------+                  |
    |        | function outerFunction |                  |
    |        +------------------------+                  |
    |                                                    |
    |   +-----------+                                    |
    |   | var later |                                    |
    |   +-----------+                                    |
    |                                                    |
    |            +--------------------------------+      |
    |            | function innerFunction() {...} |      |
    |            +--------------------------------+      |
    |                                                    |
    |             +----------------+                     |
    |             | var innerValue |                     |
    |             +----------------+                     |
    |                                                    |
    |        +----------------+                          |
    |        | var outerValue |                          |
    |        +----------------+       CLOSURE            |
    |                                                    |
    +----------------------------------------------------+
    
  • 一旦closure创建起来了以后,function就有了它运行所有需要的一切(the function has all it needs to execute), 好了,这里有一个问题了,为什么"仅仅"包括了function 定义时刻scope的内容,closure就肯定包括了function运行所需要的一切?难道function 不能使用'其他'内容么?
  • 答案是function'不一定'能使用'其他'内容,原因很简单:它看不到!function能看到的 变量有两种:
    • 看到自己定义所在的scope里面的变量,所以它内部也仅仅可能使用这些变量
    • global scope的内容(所有人都看得到)
  • 每个function都带有一个closure,所以可以在无论什么时候进行运行,但是也是有代价 的: 每个function都要在内存里面维护这么一个closure,直到js engine它不会再被需 要,或者page unload的时候

Putting closures to work

  • 我们已经从概念上了解了closure,下面来看看closure如何在js代码中起作用的例子

Mimicking private variables

  • 很多语言都有private variable这个概念,这是oo语言实现内聚的重要特性
  • 在js里面,我们通过closure来实现private variable,看下面的例子
    function Ninja() {
        var feints = 0;
        this.getFeints = function() {
            return feints;
        };
        this.feint = function() {
            feints++;
        };
    }
    
    var ninja1 = new Ninja();
    ninja1.feint();
    
    // private data is inaccessible to us
    console.log(ninja1.feints);     // undefined
    
    // We're able to access the internal feint count via getter
    console.log(ninja1.getFeints());
    
    var ninja2 = new Ninja();
    
    // ninja2 gets its own feints variable
    console.log(ninja2.getFeints());
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // undefined                                      //
    // 1                                              //
    // 0                                              //
    ////////////////////////////////////////////////////
    
  • 这个例子中,我们使用Ninja function作为一个ctor.这是一个常规操作
  • 不常规的操作在于我们在ctor里面定义了一个变量feints来hold state: 这个时候, 首先起作用的是javascript的scoping rule(不是closure是scope!!):这个变量只能 在constructor里面访问(within the ctor)
  • 好了,下面该closure出场了:为了让外面也能'访问'这个变量,我们设计了一个function 作为getter.这里体现了closure起作用的一大特点,在function里面定义function的 时候,往往意味着用到closure了!
    Whenever you use function inside another function, a closure is used.
    
  • 下面还是在function里面定义function:在Ninja()里面定义了一个setter: feint,这 里只是简单的增加1
  • 好了,我们的代码显示出,我们只能使用setter: feint来更改private variable,这跟 oo语言里面的封装是一个道理
  • ninja2的例子还告诉我们,这个variable是每个instance都有一个的–和private variable 一样
  • 使用closure(在scope和new的帮助下),ninja object的state只能通过特定的method 来访问

Using closures with callbacks

  • closure另外经常出现的地方就是callbacks(when a function is called at an unspecified later time)
  • 下面是一个callback使用closure的例子
    <body>
        <div id="box1">First Box</div>
      <script>
        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");
      </script>
    </body>
    
  • 这个例子的主体是其实就是对某个element(box1)调用一个函数animateIt, 而animateIt 的内部其主要作用的是setInterval这个函数
  • setInterval函数的作用是'间隔性'的调用某个function(这个function肯定是callback 啦), 会返回一个timerId,这个timerId以后可以传递给clearInterval()来取消这个'间 歇性'的callback调用(这里是通过判断tick到没到100下来决定的)
  • 那重点又转移到了这个callback函数上面,这个callback函数其实就是根据tick来移 动elem,所以这个函数一共用到三个variable:
    • tick:调用了多少次callback
    • elem:移动那个element
    • timer: callback返回的timer,callback一定次数之后返回
  • 我们发现这三个变量,其实是托closure的福才能访问的到,我们也发现我们的callback 函数正好是use function inside another function.
  • 那么我们有没有可能不使用closure呢?
  • 其实也可以,把三个变量都定义成全局变量就可以了,但是这会:
    • 极大的污染全局变量.
    • 如果你有两个以上的animataion,那么会相互影响!
  • 所以这是closure可以让我们做的better的一个特性(而不是像private variable那样 的没有就实现不了的特性)

Tracking code execution with execution context

  • 在js里面,最基本的单位就是function.为了完成某些功能,我们可以在一个function里 面调用另外的function,继续嵌套等等
  • 当一个function完成工作的时候,它必须退回到call 它时候的状态,学过c语言的都知 道这个是通过stack来完成的
  • 前面也说过了,我们有两种js code:
    • global code: 在所有的function之外的代码
    • function code: 在function内部的代码
  • 我们为这两种js code都准备了一种execution context:
    • global execution context: 全局只有一个
    • function execution context: 每个function创建的时候都会创建一个
  • 每次function调用的时候push一个context到stack里面,当function返回的时候,这个 context就会被pop而放弃
  • 除了保存运行的position以外,execution context还能够检查某个variable时候合法, 这是通过lexical environment来完成的

Keep track of identifiers with lexical environments

  • lexical environment是一个js engine内部的概念,所以说起来是这么的拗口,我们通 常把这个叫做scope.
  • lexical是把identifier和内存值映射的这么一个机制,有点像编译里面的变量表
  • 在es6之前,scope只能有一种,那就是function,其他c-like语言里面的`{}`内部就是一 个scope的情况,在js里面没有实现,幸好es6实现了!

Coding nesting

  • 先看下面的一个例子
    <html>
        <script>
         var ninja = "Muneyoshi";
         function skulk() {
             var action = "skulking";
             function report() {
                 var reportNum = 3;
    
                 for (var i = 0; i < reportNum; i++) {
                     console.log(ninja + " " + action + " " + i);
                 }
             }
             report();
         }
         skulk();
        </script>
    </html>
    
  • 这个例子中,我们看到了好多的nested:
    • for loop被nested在report function里面
    • report()被nested在skulk()里面
    • skulk()被nested在global code里面
  • 在scope的语义里面,每次的"调用"都会获得一个新的lexical environment,就是一个 独立的scope,这个是和c-like语言保持一致的
    In terms of scopes, each of these code structures gets an associated lexical
    environment every time the code is evaluated.
    
  • 还有一点需要强调的是,inner code'还必须可以'access to 定义在outer code的 variable,比如:
  • for需要有能力去使用:
    • report()里面的变量
    • skulk()里面的变量
    • global里面的变量
  • report()需要能够访问:
    • skulk()里面的变量
    • global里面的变量
  • skulk()只能访问:
    • global里面的变量
  • 这些也是c-like语言里面常见的能力,但是在js里面,实现方法有所不同

Code nesting and lexical environments

  • js里面的实现策略就是每一个lexical environment都会保存一个指向parent lexical environment的"指针",这样当当前的environment找不到响应变量的时候,它可以顺着 指针向上寻找,直到global scope
  • 我们把function也可以看成是一种scope,但是这个scope更加的牛逼,它可以调用已经 运行完了的数据! 它实现起来也更加的麻烦:
  • 每当一个function被创建的时候,function都会有一个叫做\[\[ Environment\]\]的 property被创建,指向这个function`被创建时期`的scope. 这样保证了,我们的function 就算当做参数传递出去,也始终能通过\[\[ Environment\]\]来记住自己的'来历'

Understanding types of JavaScript variables

  • java中,我们可以使用三种方法定义变量:
    • var
    • let
    • const
  • 它们的区别在"是否可变"以及和lexical environment的关系不同

Variable mutability

  • 那就是分成:
    • const
    • let或者var
  • const就是不可变'变量',es6代码的风格趋向于如果不确定,就把变量定义成const

Variable definition keywords and lexical environments

  • var是上古时期的产物,它对于scope是不认识的,所以它只能定义在最近的function scope 或者是global scope
  • const和let就会尊重scope啦

Registering identifiers within lexical environments

  • js是一个script语言,但是script语言其实也不是从上到下一次运行的,它还是有点静 态语言的味道:因为它会先"大概过一下代码",了解定义了哪些function,所以,下面的 代码执行效果如下
    const firstRonin = "Kiyokawa";
    check(firstRonin);
    
    function check(ronin) {
        console.log(ronin);
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Kiyokawa                                       //
    ////////////////////////////////////////////////////
    
  • js engine往往会visit代码两遍:
    • 第一遍不运行,只是把当前lexical environment的变量和function都注册一下
    • 第二遍才是正常的运行
  • 我们详细来解释下,js engine把代码的执行分成了两个phase:
    • 第一个phase被激发的条件,是new lexical environment被创建的时候,这个phase 不会执行代码,js engine会访问并注册当前lexical environment里面所有的declared variable和function
    • 第二个phase,才是真正的execution
  • 相比之下,第二个phase就是一行一行执行,比较容易理解. 而第一个phase却非常的复 杂,根据variable类型的不同,和environment的不同,都有不同的执行方法:
    • 如果我们的lexical environment是function environment,那么就会创建arguments 当然了还要给这个arguments类数组赋值.只有function environment才会做这一步
    • 如果我们的lexical environment是global或者function environment,当前的代码 主体会被扫描并注册,同时还会为每个发现的function创建一个指向当前environment的指针 扫描并注册的时候,但是注意如下两点:
      1. arrow function不会被注意到!
      2. function的body不会去检查,否则没完了
    • 如果我们的lexical environment是global或者function environment, 我们还会 去扫描variable,所有定义在global scope和function scope的var变量以及const, let变量,都会被找到.
    • 如果我们的lexical environment是block environment,那么变量方面,只有const和 let被找到

Exploring how closures work

  • 我们这一章的开始是从介绍closure开始的,closure是一种能够让function在"运行的 时候"回忆起自己在"创建的时候scope"的变量的机制
  • 你还学到了使用closure可以模拟private object,或者是让代码和callback配合的更好

Revisiting mimicking private variables with closures

  • 我们先来回顾下创建一个ninja1的代码
    function Ninja() {
        var feints = 0;
        this.getFeints = function() {
            return feints;
        };
    
        this.feint = function() {
            feints++;
        };
    }
    
    var ninja1 = new Ninja();
    
  • js的ctor其实是调用function(只不过是使用了new来调用),那么每调用一次function 我们就有一个lexical environment(这个environment会保存对这个ctor创建时期可 见的variable),这是由scope的机制决定的
  • js的evaulate一段scope(这里是调用一段function)会创建Environment,而同时js"创 建"一段function的时候,它会保持一个ref到它'创建时刻'的lexical environment(js engine内部概念)
  • 理解上面的一段文字非常重要,它说明了两个问题:
    • js的调用,本质上是一个evaluate scope的过程(曾经function是唯一的一种scope 但是在es6之后,不再是唯一了,但也是scope的一种), 每次evaluate scope都会创建 一个Environment
    • js的创建,会记录其创建时刻的Environment,在绝大多数情况下,function是会created 在global的scope,所以这个function会把global记录自己的\[\[Environment\]\]. 而这个例子的情况"非常特殊",getter和setter是建立在ctor里面的,也就是说,它们 会把ctor的Environment设计成自己的\[\[Environment\]\].ctor每次的evaluate scope都会创建一个Environment,而其中的function会记录这个Environment
  • getter和setter另外一个特殊的地方(一个是不是建立在global scope),是它们是赋予 给cotr所创建的this的,所以它们可以在ctor之外被访问(通过new赋值的变量),这样, 我们就创建了一个新的特殊的函数:
    • 定义在另外一个函数里面,所以可以访问这个函数里面的变量
    • 可以在global scope使用
  • 好了,我们继续使用下面的代码创建第二个object
    var ninja2 = new Ninja();
    
  • 那么我们Ninja()会再次调用,也就会生成新的Environment(every invocation都会产 生新的Environment), 那setter和getter也都会指向新的Environment,所以
    ninja1.getFeints mehtod is different from the ninja2.getFeints method!!
    
  • 好了,我们来总结下Environment和Closure的区别:
    • Environment可以看做是stack的一个js engine对应:记录当前可以访问的变量,在 历史上是使用stack的,所有在stack里面的(可能有好多层)变量都可以访问.但是 stack有一个致命的问题是,它可能会弹出,一旦弹出就没有存储了.对于js这种functional 语言来说是致命的(因为它要始终记得'自己创建时候'的变量可访问性),所以js engine 就设计了Environment,它内容等同于stack,但是却不会消失
    • closure是使用一个指针\[\[Environment\]\]来记录function'必须使用的variable' 而已,占了Environment的便宜,closure只需要一指针就实现了

Private variables caveat

  • 我们需要注意的是,我们使用closure实现的private variable其实只是一种"尽可能好" 的模仿而已.我们依然可以通过把一个object的function赋给另外一个object,从 而丧失任何的private特性
    function Ninja() {
        var feints = 0;
        this.getFeints = function() {
            return feints;
        };
        this.feint = function() {
            feints++;
        };
    }
    
    var ninja1 = new Ninja();
    ninja1.feint();
    
    var imposter = {};
    imposter.getFeints = ninja1.getFeints;
    
    console.log(imposter.getFeints());
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 1                                              //
    ////////////////////////////////////////////////////
    
  • 但是很多js开发者依然认为使用closure来模拟private variable的方法很有效!谁用 谁知道

Revisiting the closures and callbacks example

  • 我们再来看看上面的callback的例子,这次我们使用了两个object
    <div id ="box1">First Box</div>
    <div id ="box2">Second Box</div>
    
    <script>
     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");
     animateIt("box2");
    </script>
    
  • 每一次的function调用都会产生一个新的lexical environment,这里调用了两次animateIt 那么显然会有两个Environment产生,那么显然,每个Environment里面都会有各自的一 个elem和tick,所以两次调用也不会相互影响.这里我们就从理论上解释了为什么closure 能让callback函数更简单

Chapter 6: Functions for the future: generators and promises

Makeing our async code elegant with generators and promises

Working with generator functions

  • generator是一种完全崭新的function类型,它会generate一系列的值,但是并不是一次 返回,而是每次调用返回一次结果,知道没有结果返回了.
  • 什么时候没有结果了呢?我们新引入了一个关键字yield,你可以看做是return的特殊形 式,yield了几次,就可以返回几次.如果调用的次数超过yield的次数,那么就没有结果 返回了.generator会明确告诉调用者的,看例子
    function* WeaponGenerator() {
        yield "Katana",
        yield "Wakizashi";
        yield "Kusarigama";
    }
    
    var weapon = WeaponGenerator();
    console.log(weapon.next());
    console.log(weapon.next());
    console.log(weapon.next());
    console.log(weapon.next());
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // { value: 'Katana', done: false }               //
    // { value: 'Wakizashi', done: false }            //
    // { value: 'Kusarigama', done: false }           //
    // { value: undefined, done: true }               //
    ////////////////////////////////////////////////////
    
  • 这个例子引入了很多的新知识:
    • generator定义是使用function*来定义的
    • yield相当于"不马上返回的"的return
    • 调用这个函数不会去执行,而是返回一个object,叫做iterator.这里的的weapon object就是一个iterator,特点是自带一个函数叫做next(). 这其实就是实现了经典 的Iterator设计模式
               迭代器模式能够提高你的'遍历'的性能,从一个节点可以直接到下一个节点,遍历
               的时间复杂度是O(N).
               如果使用普通的遍历方法,每遍历一个节点就要使用get(idx)方法,会遍历半个列
               表去查找,所以整个遍历的复杂度是O(N^2)
      

Controlling the generator throught the Iterator object

  • 对generator的调用,不会去执行function的内部代码,相反,调用会产生一个iterator object: 整个是一个我们可以利用的,用来和iterator进行通信的利器
  • iterator每次都会返回一个object,其解构我们都看到了
    {
        value: 'xxx',
        done: true
    }
    
  • 我们可以把iterator暴露出来的next()函数看成是它的interface,这个函数是我们唯 一可以依赖的,但是依赖的好,会有特别好的效果, 比如我们可以使用while来判断next() 返回的的done值是不是true
    function* WeaponGenerator() {
        yield "Katana",
        yield "Wakizashi";
    }
    
    const weaponsIterator = WeaponGenerator();
    
    while(!(item = weaponsIterator.next()).done) {
        console.log(item.value);
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Katana                                         //
    // Wakizashi                                      //
    ////////////////////////////////////////////////////
    
  • 其实我们使用while的这种方法,用另外的语法糖的方法表示出来,就是for-of loop啦 for-of loop不再需要我们去直接调用next(), 自然也不再需要关注next()返回值的 结构
    function* WeaponGenerator() {
        yield "Katana",
        yield "Wakizashi";
    }
    
    for (let item of WeaponGenerator()){
        console.log(item);
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Katana                                         //
    // Wakizashi                                      //
    ////////////////////////////////////////////////////
    
  • 上面我们介绍的是最基本的generator的使用方法,其实还有一些高端的使用方法,比如 下面要介绍的把generator的execution代理给其他的generator,先看代码
    function* WarriorGenerator() {
        yield "Sun Tzu";
        yield* NinjaGenerator();
        yield "Genhis Khan";
    }
    
    function* NinjaGenerator() {
        yield "Hattori";
        yield "Yoshi";
    }
    
    for (let warrior of WarriorGenerator()) {
        console.log(warrior);
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Sun Tzu                                        //
    // Hattori                                        //
    // Yoshi                                          //
    // Genhis Khan                                    //
    ////////////////////////////////////////////////////
    
  • 注意,这里面的yield有一个是特殊的,是yield*,而不是单单的yield,使用了yield*之 后,我们的yield不再是直接返回后面的值了,而是返回后面值里面的yield.换句话说,
            就是把控制权交给了新的generator,让它去遍历,遍历完再把控制权还回来
    

Using generators

  • 生产sequences of item这件事情,看起来不起眼,但是在程序中却经常被用到,比如一 个特别常用的例子,就是生成unique的ID
  • 每当创建一个object的时候,我们都想给这个object一个不会重复的id,在没有generator 的帮助下,我们可以通过一个global counter来做到.但是这样太丑陋了,而且谁都有 权限去更改这个全局变量
  • 有了generator这种语言级别的特性,这个件事情做起来就非常容易了
    function *IdGenerator() {
        let id = 0;
        while (true) {
            yield ++id;
        }
    }
    
    const idIterator = IdGenerator();
    
    console.log(idIterator.next().value);
    console.log(idIterator.next().value);
    console.log(idIterator.next().value);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 1                                              //
    // 2                                              //
    // 3                                              //
    ////////////////////////////////////////////////////
    
  • 例子中的id是generator自己的local variable,完全不会被外界干扰
  • 虽然我们使用了while(true)这种语法,看似会产生无限循环,但是由于yield这种可以 suspend的特性,我们也不必担心无限循环的问题.
  • 在来看一个遍历DOM的例子,在没有generator之前,代码如下
    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <title>Recursive DOM traversal</title>
            <link rel="stylesheet" href="../assert.css">
            <script src="../assert.js"></script>
        </head>
        <body>
            <div id="subTree">
                <form>
                    <input type="text"/>
                </form>
                <p>Paragraph</p>
                <span>Span</span>
            </div>
            <script>
             "use strict";
    
             function traverseDOM(element, callback) {
                 callback(element);
                 element = element.firstElementChild;
                 while (element) {
                     traverseDOM(element, callback);
                     element = element.nextElementSibling;
                 }
             }
    
             const subTree = document.getElementById("subTree");
             traverseDOM(subTree, function(element) {
                 assert(element !== null, element.nodeName);
             });
            </script>
        </body>
    </html>
    
  • 怎么说呢,这个例子在我看来,挺顺眼的!反倒是使用generator看起来还真是费劲,如下
    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <title>Generator DOM traversal</title>
            <link rel="stylesheet" href="../assert.css">
            <script src="../assert.js"></script>
        </head>
        <body>
            <div id="subTree">
                <form>
                    <input type="text"/>
                </form>
                <p>Paragraph</p>
                <span>Span</span>
            </div>
    
            <script>
             "use strict";
    
             function* DomTraversal(element){
                 yield element;
                 element = element.firstElementChild;
                 while (element) {
                     yield* DomTraversal(element);
                     element = element.nextElementSibling;
                 }
             }
    
             const subTree = document.getElementById("subTree");
             for(let element of DomTraversal(subTree)) {
                 assert(element !== null, element.nodeName);
             }
            </script>
        </body>
    </html>
    

Communicating with a generator

  • 前面的例子,generator是一个纯的"输出"的对象,如果加上输入,那就是双向的,就能 称得上'交流'了
  • 我们和generator交流的方式有两种:
    • 第一种比较简单,就是从function传入参数了, generator也是函数,自然可以读取 输入参数
    • 第二种就比较麻烦了,是从next()函数里面传入参数,这个传入的参数"赋值给了上次 next的调用".看清楚了,是上次next的调用,所以第一次使用next()就传参数都没地 方收, 但是第一次next()调用你可以使用generator function的参赛直接传入进去!
  • 两种调用方式在下面的例子中均有体现
    function* NinjaGenerator(action) {
        const imposter = yield ("Hattori " + action);
    
        if (imposter === "Hanzo") {
            console.log("The generator has been infiltrated");
        }
    
        yield("Yoshi (" + imposter + ") " + action);
    }
    
    const ninjaIterator = NinjaGenerator("skulk");
    
    console.log(ninjaIterator.next());
    console.log(ninjaIterator.next("Hanzo"));
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // { value: 'Hattori skulk', done: false }        //
    // The generator has been infiltrated             //
    // { value: 'Yoshi (Hanzo) skulk', done: false }  //
    ////////////////////////////////////////////////////
    

Exploring generatos under the hood

  • TODO

Working with promises

  • 在js中,我们很依赖asynchronous计算,所谓asynchronous计算就是说,当前无法知道计 算的结果,但是later一定会知道结果.
  • es6新进引入的promise就是来处理asynchronous task的object
  • 我们可以把promise看成是一种placeholder,用来存放我们当前还不知道结果的值,promise 保证我们最后会得到一个结果,因为最坏的结果也就是失败,或者是是一个我们为啥没 办法deliver的excuse
  • 一个最常见例子就是使用promise来从server获取data,我们保证能够从服务器获取数据, 当然server也可能出现问题,但是我们保证我们对调用者有个交代
  • 下面是一个promise的例子
    const ninjaPromise = new Promise((resolve, reject) => {
        if (Math.random() > 0.5) {
            resolve("Hattori");
        } else {
            reject("An error happened");
        }
    });
    
    ninjaPromise.then(ninja => {
        console.log(ninja);
    }, err => {
        console.log(err);
    });
    
    ///////////////////////////////////////////////////////////////
    // <===================possible OUTPUT 1===================> //
    // Hattori                                                   //
    ///////////////////////////////////////////////////////////////
    
    
    ///////////////////////////////////////////////////////////////
    // <===================possible OUTPUT 2===================> //
    // An error happened                                         //
    ///////////////////////////////////////////////////////////////
    
  • 为了创建一个promise,我们使用了内置的Promise constructor, 这个ctor要求传入一 个function作为参数(我们这里使用的是arrow function)
  • 这个传入的function参数叫做executor function, 有两个参数(其实也是两个函数):
    • resolve
    • reject
  • 这个executor function会在Promise object创建的那一刻'马上调用',这个function 一般都会根据"情势"来决定到底是调用resolve函数还是reject函数,通常是正常的情 况调用resolve,异常的情况调用reject
  • 好了,promise在创建了以后就返回了,object ref存在变量ninjaPromise里面,对这个 object的使用是调用这个object的函数then(),这个函数也是有两个callback作为参数:
    • success callback: resolve()调用的时候使用
    • failure callback: reject()调用的时候使用

Understanding the problems with simple callbacks

  • 我们的js代码通常对响应性要求很高,所以当long-running task执行的时候,我们不 想block当前的application运行.当前的做法是把这个long-running task排挤出当前 运行的stack,然后为这个long-running task准备一个callback function,当这个task 运行完的时候,我们运行callback就好了
  • 比如,从服务器获取json file是一个典型的long-running task,为了不让我们获取json 的时候app变得unresponsive,我们必须为这个task配备一个callback
    getJSON("data/ninjas.json", function() {
        /* Handle results */
    });
    
  • 但是问题在于,这个long-running task有可能是会发生错误的,但是我们不能使用try catch来处理这个问题,如下
    try {
        getJSON("data/ninjas.json", function() {
            // Handle results
        });
    } catch(e) {
        // Handle errors
    }
    
  • 不能使用try-catch的原因在于,如下两段代码并不在同一个event loop里面:
    • invoking callback的代码
    • start the long-running task的代码
  • TODO

Diving into promises

  • 看一个结合了delayjob和普通job的promise的例子
    console.log("At code start--------->");
    
    var ninjaDelayedPromise = new Promise((resolve, reject) => {
        console.log("ninjaDelayedPromise executor");
        setTimeout(() => {
            console.log("Resolving ninjaDelayedPromise");
            resolve("Hattori");
        }, 500);
    });
    
    console.log(ninjaDelayedPromise);
    
    ninjaDelayedPromise.then(ninja => {
        console.log("ninjaDelayedPromise resolve handled with " + ninja);
    });
    
    const ninjaImmediatePromise = new Promise((resolve, reject) => {
        console.log("ninjaImmediatepromise executor");
        resolve("Yoshi");
    });
    
    console.log(ninjaImmediatePromise);
    
    ninjaImmediatePromise.then(ninja => {
        console.log("ninjaImmediatepromise resolve handled with " + ninja);
    });
    
    console.log("At code end----------->");
    
    //////////////////////////////////////////////////////
    // <===================OUTPUT===================>   //
    // At code start--------->                          //
    // ninjaDelayedPromise executor                     //
    // Promise { <pending> }                            //
    // ninjaImmediatepromise executor                   //
    // Promise { 'Yoshi' }                              //
    // At code end----------->                          //
    // ninjaImmediatepromise resolve handled with Yoshi //
    // Resolving ninjaDelayedPromise                    //
    // ninjaDelayedPromise resolve handled with Hattori //
    //////////////////////////////////////////////////////
    
  • 我们首先建立了ninjaDelayedPromise,这个ctor里面使用了setTimeout来在500毫秒 以后对我们的代码进行resolve."然后马上返回"
  • 注意,setTimer这种操作都是马上返回,放弃对cpu的控制的,然后在系统级别注册一个 中断,在500毫秒以后,中断会发送给浏览器,浏览器会把这个中断放入event queue,然 后最终终究会被执行
  • 当ninjaDelayedPromise被创建的时候,它是不知道自己的value的,所以这个时候,我们 打印出来就发现value为{ <pending> }
  • 然后我们来调用then函数,then函数里面也会注册"成功后"的callback,要注意,这个callback 无论如何都不会马上运行的,这是js engine的设计决定的,任何then函数都会在当前event loop之后再运行
    Promises are designed to deal with asynchronous actions, so the
    JavaScript engine always resorts to asynchronous handling, to
    make the promise behavior predictable.The engine does this by executing
    the then callbacks after all the code in the current steop of
    event loop is executed.
    
  • 后面我们又创建了一个ninjaImmediatepromise, 这次我们的resolve是马上执行了, 所以我们的promise是立马有值的,也打印出来了.但是这并不能保证我们的then函数 马上实现, 它还是等到后面才实现,只不过比ninjaDelayedPromise的then函数早一点 罢了

Rejecting promises

  • TODO

Creating our firs real-world promise

  • TODO

Chaining promises

  • TODO

Waiting for a number of promises

  • TODO

Combining generators and promises

  • TODO

Chapter 7: Object orientation with prototypes

  • 你已经了解到了function是js里面的first-class,做到first-class还依靠了closure的 灵活性
  • 现在,你要了解js另外一个重要的特性了,就是object prototype
  • 首先,prototype是一个object,别的普通object可以把寻找某个specific property的任 务代理给prototype.
  • 我们其实可以把property看做是其他oo语言里面的class的作用
    Prototypes serve a similar purpose to that of classes in classical
    object-oriented languages
    

Understanding prototypes

  • 在js里面,object就是一系列property和value的组合体,value可以是:
    • simple value
    • function
    • another object
  • 例子如下
    let obj = {
        prop1: 1,
        prop2: function(){},
        prop3: {}
    }
    
  • 我们还可以几乎不受控制的对object进行"增删改查"
    obj.prop1 = 1;
    obj.prop1 = [];
    delete obj.prop2;
    obj.prop4 = "Hello";
    
  • 得到结果如下
    {
        prop1: [],
        prop3: {},
        prop4: "Hello"
    };
    
  • 继承,是oo编程的重要概念,能够极大的减少代码的重复.js正是通过prototype来实现 了继承.我们前面说过,prototype说白了也是一个object,那我们如何使用object来完 成inheritance呢?
  • 原理在于js重新定义了property search技术, 对于一个property的寻找:
    • 首先在自己的object体内寻找,如果找到就用
    • 如果不在自己体内,就去'自己体内一个特殊的object'里面去寻找,这个'特殊的object' 就是prototype
  • 通过下面的例子,那就可以发现,prototyp'只不过是一个普通object',只是被其他object 设置为'plan B'的property寻找地
    const yoshi = {
        skulk: true
    };
    const hattori = {
        sneak: true
    };
    const kuma = {
        creep: true
    };
    
    console.log("skulk" in yoshi);
    console.log("sneak" in yoshi);
    console.log("creep" in yoshi);
    console.log('------------------------');
    
    Object.setPrototypeOf(yoshi, hattori);
    
    console.log("skulk" in yoshi);
    console.log("sneak" in yoshi);
    console.log("creep" in yoshi);
    console.log('------------------------');
    
    Object.setPrototypeOf(yoshi, kuma);
    
    console.log("skulk" in yoshi);
    console.log("sneak" in yoshi);
    console.log("creep" in yoshi);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // true                                           //
    // false                                          //
    // false                                          //
    // ------------------------                       //
    // true                                           //
    // true                                           //
    // false                                          //
    // ------------------------                       //
    // true                                           //
    // false                                          //
    // true                                           //
    ////////////////////////////////////////////////////
    
  • js里面, 某个object的prototype 也是一个property,而且这个property是普通人看不 到的,不能直接访问的,被记做\[\[prototype\]\]
  • 通过Object.setPrototypeOf的帮助,我们可以在自己'体内'找不到property的情况下, 去\[\[prototype\]\]指定的object里面去寻找,如下图
    +---------------+           +---------------+
    |     yoshi     |           |    hattori    |
    |               |           |               |
    |skulk: true    +---------->|sneak: true    |
    |[[prototype]]  |           |               |
    +---------------+           +---------------+
    

Object construction and prototypes

  • js里面创建一个object是很简单的,对其操作也是可以直接的
    const warrior = {};
    
    warrior.name = 'Saito';
    warrior.occupation = 'marksman';
    
  • oo背景的人,会非常想念class的概念,在创建'某一类'特别相似的object的时候,使用 class会极大的减少复杂度.js也提供了继承的概念,但是不是使用class,而是使用ctor 然后用new来调用ctor,就模拟了oo语言里面new一个class
  • 使用new + ctor创建一个object,然后ctor里面'建设'这个object,是我们前面学到的, 但是我们前面没学到的是:
    ctor的名字叫prototype的property,会被自动设置为新创建object的[[prototype]].
    
  • 前面说过,每个object都有\[\[prototype\]\],但是它不能去直接操作,但是如果这个 object来自某个ctor,这个ctor是可以替它操作\[\[prototype\]\]的.
    function Ninja(){}
    
    Ninja.prototype.swingSword = function() {
        return true;
    };
    
    const ninja1 = Ninja();         // none new
    console.log(ninja1);
    
    const ninja2 = new Ninja();
    console.log("swingSword" in ninja2);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // undefined                                      //
    // true                                           //
    ////////////////////////////////////////////////////
    
  • 上面的例子中,我们直接使用了Ninja.prototype, 这是因为prototype(不加[])是每个 function都有的property(因为每个function都是个"潜力股",都可以被new调用), 这个 property作为一个object,其实也是有自带的一个property的,就是constructor,指向 包含它的函数也就是Ninja.换句话说,Ninja和prototype是"双向链接"的
                       +--------------------+
                       | function Ninja(){} |
                       | prototype:---+     |<-----------+
                       +--------------+-----+            |
                                      |                  |
                                      |                  |
                                      v                  |
    +---------------+           +-------------------+    |
    |     ninja2    |           |  Ninja prototype  |    |
    |     ------    |           |  ---------------  |    |
    |               |           |  constructor------+----+      +-------------+
    | [[prototype]]-+---------->|  swingSword ------+---------->|function(){} |
    +---------------+           +-------------------+           +-------------+
    
  • 注意,只有object的prototype property是加[]的,内置的.函数的prototype就是普通 的property

Instance properties

  • 我们知道,一个object寻找method的过程被分成了两部分:
    • 从ctor的method里面寻找
    • 从自己的prototype里面去寻找
  • 如果两个部分里面都有名字相同的函数(我们知道js是没有overload的)的话,我们选 哪个呢?看例子
    function Ninja() {
        this.swingSword = function() {
            console.log("in the ctor");
        };
    }
    
    Ninja.prototype.swingSword = function() {
        console.log("in the ctor's prototype");
    };
    
    const ninja = new Ninja();
    ninja.swingSword();
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // in the ctor                                    //
    ////////////////////////////////////////////////////
    
  • 结果证明,ctor里面的函数有更高的优先权.那是不是method要更主要的放在ctor里面 呢?
  • 结果恰恰相反:
    • 如果是为了使用定义在ctor里面的method和variable来实现getter和setter private variable的话,我们只能把method定义在ctor里面
    • 其他所有的其他情况下,我们都要尽量把method定义在cotr的prototype里面.原因 如下,如果我们有多个ninja instance的情况下,ctor的property都会维护一份自己 的拷贝, 对于value来说比较正常,对于method来说,就显得重复,因为函数是一种不 带有"副作用"的操作,定义一份就可以了
                          +---------------+           +-------------------+
                          |    ninja1     |           |function Ninja(){} |
      +------------+      |    ------     |           |prototype:---+     |<-----+
      |function(){}|<-----+-swingWord     |           +-------------+-----+      |
      +------------+      | [[prototype]]-+-----+                   |            |
                          +---------------+     |                   |            |
                                                |     +-------------------+      |
                                                |     | Ninja prototype   |      |
                          +---------------+     |---> | ---------------   |      |
                          |    ninja1     |     |     | constructor ------+------|
      +------------+      |    ------     |     |     | swingSword  ------+-+
      |function(){}|<-----+--wingWord     |     |     +-------------------+ |
      +------------+      | [[prototype]]-+-----+                           |
                          +---------------+     |                           |
                                                |                           |
                                                |                           |
                          +---------------+     |                           |
                          |    ninja1     |     |                           |
      +------------+      |    ------     |     |                           |   +------------+
      |function(){}|<-----+--wingWord     |     |                           +-> |function(){}|
      +------------+      | [[prototype]]-+-----+                               +------------+
                          +---------------+
      

Side effects of the synamic nature of JavaScript

  • 作为动态语言,一个object的property可以随意的被"增删改查",prototype也是一个 property,所以也免不了相同的命运.
  • 而这种动态的特性会引入很多的问题,如果不熟悉的话,会感到很困惑,比如下面的例子
    function Ninja() {
        this.swung = true;
    }
    
    const ninja1 = new Ninja();
    
    Ninja.prototype.swingSword = function() {
        return this.swung;
    };
    
    // the method is added after the object creation
    console.log(ninja1.swingSword());
    
    Ninja.prototype = {
        pierce:function() {
            return true;
        }
    };
    
    // still see the method, as we keep the ref to old prototype
    console.log(ninja1.swingSword());
    
    // newly created ninjas refer to new prototyp, forget about swing
    const ninja2 = new Ninja();
    console.log("pierce" in ninja2);
    console.log("swingSword" in ninja2);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // true                                           //
    // true                                           //
    // true                                           //
    // false                                          //
    ////////////////////////////////////////////////////
    

Object typing via constructors

  • 我们前面就说到过,我们可以通过一个叫做constructor的property(存储在object的 prototype里面)来找到我们的object是从哪个ctor创建来的
  • 这个constructor property能够让我们使用instanceof来判断自己的类型
    function Ninja() {}
    const ninja = new Ninja();
    
    console.log(ninja.constructor);
    console.log(ninja.constructor === Ninja);
    console.log(ninja instanceof Ninja);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // [Function: Ninja]                              //
    // true                                           //
    // true                                           //
    ////////////////////////////////////////////////////
    

Achieving inheritance

  • 在es6之前,想要做到"继承"非常的麻烦,因为js模拟oo语言的机制涉及到两个变量:
    • propertype
    • constructor
  • 所以我们要"手动"设置这两个变量,代码看起来就非常的麻烦
    function Person() {}
    Person.prototype.dance = function() {};
    
    function Ninja() {}
    Ninja.prototype = new Person();
    
    Object.defineProperty(Ninja.prototype, "constructor", {
        enumerable: false,
        value: Ninja,
        writable: true
    });
    
    var ninja = new Ninja();
    
    console.log(ninja.constructor === Ninja);
    console.log("dance" in Ninja.prototype);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // true                                           //
    // true                                           //
    ////////////////////////////////////////////////////
    

Using JavaScript "classes" in ES6

  • 如此麻烦的代码,让人无法忍受,所以es6引入了class这个概念,因为javascript为了赶 时髦,故意模仿java,所以把所有java的关键字都作为了自己的关键字.这样一来,引入 新的关键字毫无压力
  • 但是class并没有新的架构,只不过是语法糖罢了:
    • 一个简单的class例子:
      class Ninja{
          constructor(name) {
              this.name = name;
          }
          swingSword() {
              return true;
          }
      }
      
    • 其实就相当于如下:
      function Ninja(name) {
          this.name = name;
      }
      
      Ninja.prototype.swingSword = function() {
          return true;
      }
      
  • 其实总结起来很简单:
    • 原来在ctor里面写的,现在写在constructor里面
    • 原来在prototype里面写的,现在写在{}里面
  • 现在还可以写static函数:
    class Ninja {
        static compare(ninja1, ninja2) {
            // ...
        }
    }
    
  • 其实就是原来ctor的函数的"函数property"
    function Ninja() {}
    Ninja.compare = function(ninja1, ninja2) {...}
    
  • class的引入更重要的是让"继承"更加简单了
    class Person {
        constructor(name) {
            this.name = name;
        }
    
        dance() {
            return true;
        }
    }
    
    class Ninja extends Person {
        constructor(name, weapon) {
            super(name);
            this.weapon = weapon;
        }
    
        wieldWeapon() {
            return true;
        }
    }
    
    var person = new Person("Bob");
    console.log(person instanceof Person);
    console.log(person.dance());
    console.log(person.name);
    
    console.log("---------------------------");
    
    var ninja = new Ninja("Yoshi", "Wakizashi");
    console.log(ninja instanceof Ninja);
    console.log(ninja.wieldWeapon());
    console.log(ninja instanceof Person);
    console.log(ninja.dance());
    console.log(ninja.name);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // true                                           //
    // true                                           //
    // Bob                                            //
    // ---------------------------                    //
    // true                                           //
    // true                                           //
    // true                                           //
    // true                                           //
    // Yoshi                                          //
    ////////////////////////////////////////////////////
    

Chapter 8: Controlling acces to objects

  • 通过前面的章节,我们了解到,在js里面,object非常的灵活,可以增加更改,甚至删除object 的property,本章我们学习管理我们object的办法(控制访问,监督改变,等等)

Controlling access to properties with getters and setters

  • 先看下面的代码
    function Ninja(level) {
        this.skillLevel = level;
    }
    const ninja = new Ninja(100);
    
  • 这里,我们定义了ctor,在初始化object ninja的时候,我们赋予了这个object一个100, 作为初始化值来初始化object的内部state: skillLevel.如果我们想更改object的内 部state,我们可以使用最简单的代码,如下
    ninja.skillLevel = 20;
    
  • 上面的做法过于的随意,所以我们希望能够做到如下的'限制':
    • 我们希望能够正确的设置property,比如我们不希望把level设置成字符串,比如
      ninja.skillLevel = "high"
      
    • 我们希望log所有对于skillLevel的改动
    • 我们要能可以获得skillLevel的值
  • 满足上面三个'限制'要求的,其实就是传统意义上的getter和setter,我们来看看如何 实现一个最naive的版本
    function Ninja() {
        let skillLevel;
    
        this.getSkillLevel = () => skillLevel;
    
        this.setSkillLevel = value => {
            skillLevel = value;
        };
    }
    
    const ninja = new Ninja();
    ninja.setSkillLevel(100);
    console.log(ninja.getSkillLevel());
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 100                                            //
    ////////////////////////////////////////////////////
    
  • 把skillLevel设置成"private"从而只能使用getter和setter访问,这是我们前面学到 过的知识了,而如果我们想log所有的访问,也很简单:在setter和getter里面log就可以 了,因为所有的访问只能通过做两个函数
    function Ninja() {
        let skillLevel;
        this.getSkillLevel = () => {
            console.log("Getting skill level value");
            return skillLevel;
        };
    
        this.setSkillLevel = value => {
            console.log("Modifying skillLevel property from: ",
                skillLevel, "to: ", value);
            skillLevel = value;
        };
    }
    
    const ninja = new Ninja();
    ninja.setSkillLevel(100);
    console.log(ninja.getSkillLevel());
    
    /////////////////////////////////////////////////////////////
    // <===================OUTPUT===================>          //
    // Modifying skillLevel property from:  undefined to:  100 //
    // Getting skill level value                               //
    // 100                                                     //
    /////////////////////////////////////////////////////////////
    
  • 这看起来很完美了,但是用户希望更简洁的方式使用setter和getter,就是使用一个 ninja.skillLevel来进行set和get.但是同时还希望保留比如log这种功能.这个时候js 就设计出了"实际上是函数(所以可以在里面写log)",但"用起来当property一样"的"理 想型"的setter getter

Defining getters and setters

  • 在js里面,上面我们提到的"理想型的"setter和getter可以使用如下两种方式定义:
    • 在object里面,使用object literal(或者ES6的class语法)
    • 使用内置的一个叫做Object.defineProperty的方法
  • 首先看看object literal的方式
    const ninjaCollection = {
        ninjas: ["Yoshi", "Kuma", "Hattori"],
        get firstNinja(){
            console.log("Getting firstNinja");
            return this.ninjas[0];
        },
        set firstNinja(value) {
            console.log("Setting firstNinja");
            this.ninjas[0] = value;
        }
    };
    
    console.log(ninjaCollection.firstNinja);
    
    ninjaCollection.firstNinja = "Hachi";
    
    console.log(ninjaCollection.firstNinja);
    console.log(ninjaCollection.ninjas[0]);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Getting firstNinja                             //
    // Yoshi                                          //
    // Setting firstNinja                             //
    // Getting firstNinja                             //
    // Hachi                                          //
    // Hachi                                          //
    ////////////////////////////////////////////////////
    
  • 好,我们再看看使用ES6的class语法,效果如下
    class NinjaCollection {
        constructor() {
            this.ninjas = ["Yoshi", "Kuma", "Hattori"];
        }
        get firstNinja() {
            console.log("Getting firstNinja");
            return this.ninjas[0];
        }
        set firstNinja(value) {
            console.log("Setting firstNinja");
            this.ninjas[0] = value;
        }
    };
    
    const ninjaCollection = new NinjaCollection();
    
    console.log(ninjaCollection.firstNinja);
    
    ninjaCollection.firstNinja = "Hachi";
    
    console.log(ninjaCollection.firstNinja);
    console.log(ninjaCollection.ninjas[0]);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Getting firstNinja                             //
    // Yoshi                                          //
    // Setting firstNinja                             //
    // Getting firstNinja                             //
    // Hachi                                          //
    // Hachi                                          //
    ////////////////////////////////////////////////////
    
  • object literal和es6 class中定义setter和getter的语法其实是一致的.我们同时还 知道,在class里面定义函数,其实就相当于在proptype里面定义函数,定义的函数是'无 法访问function的private variable'的,所以我们前面的使用closure来模拟private variable的方法在这里行不通!需要其他的手段
  • 其他的手段就是Object.defineProperty方法啦
    function Ninja() {
        let _skillLevel = 0;
    
        Object.defineProperty(this, 'skillLevel', {
            get: () => {
                console.log("The get method is called");
                return _skillLevel;
            },
            set: value => {
                console.log("The set method is called");
                _skillLevel = value;
            }
        });
    }
    
    const ninja = new Ninja();
    
    console.log(typeof ninja._skillLevel);
    console.log(ninja.skillLevel);
    ninja.skillLevel = 10;
    console.log(ninja.skillLevel);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // undefined                                      //
    // The get method is called                       //
    // 0                                              //
    // The set method is called                       //
    // The get method is called                       //
    // 10                                             //
    ////////////////////////////////////////////////////
    
  • 使用defineProperty的方法定义函数和'object literal以及class'的不同,就在于他 们和'private variable'定义在同一个scope(function scope),所以可以看到并访问 'private variable'

Using getters and setters to validate property values

  • 我们知道,getter和setter设置了以后,会成为我们访问'内部state'是唯一的方式,那 么,我们就可以在这一条"必经之路"上做些文章,比如validate传入的数据
    function Ninja() {
        let _skillLevel = 0;
    
        Object.defineProperty(this, 'skillLevel', {
            get: () => _skillLevel,
            set: value => {
                if(!Number.isInteger(value)) {
                    throw new TypeError("Skill level should be a number");
                }
                _skillLevel = value;
            }
        });
    }
    
    const ninja = new Ninja();
    
    ninja.skillLevel = 10;
    console.log(ninja.skillLevel);
    
    try {
        ninja.skillLevel = "Great";
        console.log("Failed!, should not be here");
    } catch(e) {
        console.log("Passed! setting a non-integer value throws an exception");
    }
    
    /////////////////////////////////////////////////////////////
    // <===================OUTPUT===================>          //
    // 10                                                      //
    // Passed! setting a non-integer value throws an exception //
    /////////////////////////////////////////////////////////////
    
  • getter和setter还有一种用法,就是计算computed property:不是简单的设置和返回 内部的state,而是把内部的state进行'计算'后返回或设置
    const shogun = {
        name: "Yoshiaki",
        clan: "Ashikaga",
    
        get fullTitle() {
            return this.name + " " + this.clan;
        },
    
        set fullTitle(value) {
            const segments = value.split(" ");
            this.name = segments[0];
            this.clan = segments[1];
        }
    };
    
    console.log(shogun.name);
    console.log(shogun.clan);
    console.log(shogun.fullTitle);
    
    console.log("----------------");
    shogun.fullTitle = "Leyasu Tokugawa";
    
    console.log(shogun.name);
    console.log(shogun.clan);
    console.log(shogun.fullTitle);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Yoshiaki                                       //
    // Ashikaga                                       //
    // Yoshiaki Ashikaga                              //
    // ----------------                               //
    // Leyasu                                         //
    // Tokugawa                                       //
    // Leyasu Tokugawa                                //
    ////////////////////////////////////////////////////
    

Using proxies to control access

  • 我们的getter和setter都是去保护对一个object的'读取和吸入',从更广义上来讲'proxy' 这个概念是能保护'所有的访问,包括读取和写入', 公司内网都是通过proxy和外部接 触的,从而保护内网的机器
  • es6在js里面也引入了proxy这个概念,用来"保护所有对object的访问", 包括:
    • 读取
    • 写入
    • method call
  • 我们先来看看一个proxy的例子
    const emperor = {
        name: "Komei"
    };
    const representative = new Proxy(emperor, {
        get: (target, key) => {
            console.log("Reading " + key + " through a proxy");
            return key in target ? target[key] :
                "Don't bother the emperor!";
        },
        set: (target, key, value) => {
            console.log("Writing " + key + " through a proxy");
            target[key] = value;
        }
    });
    
    console.log(emperor.name);
    console.log("--------------------------------");
    console.log(representative.name);
    console.log("--------------------------------");
    console.log(emperor.nickname);
    console.log("--------------------------------");
    console.log(representative.nickname);
    console.log("--------------------------------");
    representative.nickname = "Tenno";
    console.log(emperor.nickname);
    console.log("--------------------------------");
    console.log(representative.nickname);
    
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Komei                                          //
    // --------------------------------               //
    // Reading name through a proxy                   //
    // Komei                                          //
    // --------------------------------               //
    // undefined                                      //
    // --------------------------------               //
    // Reading nickname through a proxy               //
    // Don't bother the emperor!                      //
    // --------------------------------               //
    // Writing nickname through a proxy               //
    // Tenno                                          //
    // --------------------------------               //
    // Reading nickname through a proxy               //
    // Tenno                                          //
    ////////////////////////////////////////////////////
    

Using proxie for logging

  • todo

Using proxies for measuring performance

  • todo

Using proxies to autopopulate properties

  • todo

Using proxies to implement negative array Indexes

  • todo

Chapter 9: Dealing with collections

Arrays

  • 数组是最常见的数据结构了,基本所有的语言里面都有数组
  • 在javascript里面,数组其实就是一个object:
    • 这一方面会降低数组的性能
    • 这另一方面能让数组有些function可供调用

Creating arrays

  • 主要有两种方法来创建array:
    • 使用内置的Array constructor
    • 使用literals []
  • 例子如下
    const ninjas = ["Kuma", "Hattori", "Yagyu"];
    const samurai = new Array("Oda", "Tomoe");
    
    console.log(ninjas.length);     // 3
    console.log(samurai.length);    // 2
    
    console.log(ninjas[0]);         // Kuma
    console.log(samurai[samurai.length-1]); // Tomoe
    
    console.log(ninjas[4]);         // undefined
    ninjas[4] = "Ishi";
    console.log(ninjas.length);     // 5
    
    ninjas.length = 2;
    console.log(ninjas.length);     // 2
    console.log(ninjas[0]);         // Kuma
    console.log(ninjas[2]);         // undefined
    
    ninjas.length = 5;
    console.log(ninjas.length);     // 5
    console.log(ninjas[0]);         // Kuma
    console.log(ninjas[2]);         // undefined
    
  • 这个例子最令我们震撼的是,我们竟然可以通过设置数组的长度来去掉后面某些item, 注意,这里不是像c语言一样会把内存放在那里不去清理,一旦设置了length,后面的内 存就被收走了"Yagyu"再也找不到了

Adding and removing Items at either end of an array

  • 添加删除数组的成员从两头都可以:
    • push() adds an item to the end of array
    • pop() removes an item from the end of array
    • unshift() add an item to the beginning of the array
    • shift() removes an item from the beginning of the array.

Adding and removing items at any array location

  • js里面的delete函数很特别,它其实不是其他语言里面删除一个数组成员,而是把某个 成员设置成undefined
    const ninjas = ["Yagyu", "Kuma", "Hattori", "Fuma"];
    
    delete ninjas[1];
    
    console.log(ninjas.length);
    console.log(ninjas[0]);
    console.log(ninjas[1]);
    console.log(ninjas[2]);
    console.log(ninjas[3]);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 4                                              //
    // Yagyu                                          //
    // undefined                                      //
    // Hattori                                        //
    // Fuma                                           //
    ////////////////////////////////////////////////////
    
  • 上面的例子用图画表示就是
    const ninjas = ["Yagyu", "Kuma", "Hattori", "Fuma"];
    +-----------+-----------+-----------+-----------+
    |   Yagyu   |    Kuma   |  Hattori  |    Fuma   |
    +-----------+-----------+-----------+-----------+
    
    delete ninjas[1];
    +-----------+-----------+-----------+-----------+
    |   Yagyu   | undefined |  Hattori  |    Fuma   |
    +-----------+-----------+-----------+-----------+
    
  • js里面真正起到删除作用的是splice, 它的返回值就是'真正删除掉的sub数组'
    const ninjas = ["Yagyu", "Kuma", "Hattori", "Fuma"];
    
    var removedItems = ninjas.splice(1, 1);
    
    console.log(removedItems);
    
    console.log(ninjas.length);
    console.log(ninjas);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // [ 'Kuma' ]                                     //
    // 3                                              //
    // [ 'Yagyu', 'Hattori', 'Fuma' ]                 //
    ////////////////////////////////////////////////////
    
  • splice还可以"在删除的同时,在删除的位置再添加数据",返回值还是删除了的子数组
    const ninjas = ["Yagyu", "Kuma", "Hattori", "Fuma"];
    
    var removedItems = ninjas.splice(1, 2, "Mochizuki", "Yoshi", "Momochi");
    
    console.log(removedItems);
    console.log(ninjas);
    
    //////////////////////////////////////////////////////////
    // <===================OUTPUT===================>       //
    // [ 'Kuma', 'Hattori' ]                                //
    // [ 'Yagyu', 'Mochizuki', 'Yoshi', 'Momochi', 'Fuma' ] //
    //////////////////////////////////////////////////////////
    

Common operations on arrays

ITERATING OVER ARRAYS
  • 对数组来说,遍历是最常见的操作,最开始的时候js是使用和c差不多的循环进行遍历 的:
    const ninjas = ["Kuma", "Hattori", "Yagyu"];
    
    for (let i = 0; i < ninjas.length; i++) {
        console.log(ninjas[i]);
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Kuma                                           //
    // Hattori                                        //
    // Yagyu                                          //
    ////////////////////////////////////////////////////
    
  • 后来js引入了更加functional的forEach,参数是一个"马上被调用"的callback
    const ninjas = ["Kuma", "Hattori", "Yagyu"];
    
    ninjas.forEach(ninja => {
        console.log(ninja);
    });
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Kuma                                           //
    // Hattori                                        //
    // Yagyu                                          //
    ////////////////////////////////////////////////////
    
MAPPING ARRAYS
  • map reduce也是functional语言里面的重要概念,js也引入了.引入方法和forEach一 样:得益于array就是一个object,直接在Array.propotype里面添加这个函数,那么所 有的array就都可以调用这个函数了
  • 来看看map的调用例子
    const ninjas = [{
        name: "Yagyu",
        weapon: "shuriken"
    }, {
        name: "Yoshi",
        weapon: "katana"
    }, {
        name: "Kuma",
        weapon: "wakizashi"
    }];
    
    const weapons = ninjas.map(ninja => ninja.weapon);
    
    console.log(weapons);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // [ 'shuriken', 'katana', 'wakizashi' ]          //
    ////////////////////////////////////////////////////
    
AGGREGATING ARRAY ITEMS
  • 有了map, reduce还远么
    [1, 3, 5, 7, 2, 4, 6].reduce((sum, cur) => sum + cur) // 28
    
TESTING ARRAY ITEMS
  • ruby里面有个叫做all?的函数,就是通过一个block来判断数组的每个成员是不是都 是满足block里面的要求
    [1, 2, 3, 4, 5].all? { |one| one >= 1 } #=> true
    [1, 2, 3, 4, 5].all? { |one| one > 1 } #=> false
    
  • js里面借鉴这个all?的功能,设计了一个Array.propotype.every()
    [1, 2, 3, 4, 5].every(one => one >= 1); // true
    [1, 2, 3, 4, 5].every(one => one > 1); // false
    
  • 同样的ruby里面还有一个判断某个成员满足要求的any?
    [1, 2, 3, 4, 5].any? { |one| one >= 5 } #=> true
    [1, 2, 3, 4, 5].any? { |one| one > 5 } #=> false
    
  • js的对应者是some()
    [1, 2, 3, 4, 5].some(one => one >= 5); // true
    [1, 2, 3, 4, 5].some(one => one > 5); // false
    
SEARCHING ARRAYS
  • ruby里面就有find(也叫detect)命令
    (1..10).detect   { |i| i % 5 == 0 and i % 7 == 0 }   #=> nil
    (1..100).find    { |i| i % 5 == 0 and i % 7 == 0 }   #=> 35
    
  • js的对应还是find
    const ninjas = [
        {name: "Yagyu", weapon: "shuriken"},
        {name: "Yoshi"},
        {name: "Kuma", weapon: "wakizashi"}
    ];
    
    console.log(ninjas.find(ninja => ninja.weapon === "wakizashi"));
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // { name: 'Kuma', weapon: 'wakizashi' }          //
    ////////////////////////////////////////////////////
    
  • find命令(无论ruby还是js)都是只返回一个结果,如果想返回多个结果:
    • ruby里面是select
      [1,2,3,4,5].select { |num|  num.even?  }   #=> [2, 4]
      
    • js里面是filter
      [1, 2, 3, 4, 5].filter(one => one % 2 === 0) // [ 2, 4 ]
      
  • 寻找array成员的index使用indexOf和lastIndexOf
    [1, 2, 3, 4, 5, 1].indexOf(1)   // 0
    [1, 2, 3, 4, 5, 1].lastIndexOf(1) // 5
    
SORTING ARRAYS
  • sorting是经常拿来举例子的功能,相比大家非常熟悉:
    [1, 3, 5, 7, 2, 4, 6].sort((a, b) => a - b) // [ 1, 2, 3, 4, 5, 6, 7 ]
    
  • js的sort有一定的内在逻辑:
    • 如果callback返回值小于0,那么a就b前面(自然顺序)
    • 如果callback返回值为0,a和b是平等的
    • 如果callback返回值大于0,那么a就在b后面
  • 所以如果想逆序排序呢,就是后面减去前面
    [1, 3, 5, 7, 2, 4, 6].sort((a, b) => b - a) // [ 7, 6, 5, 4, 3, 2, 1 ]
    

Resuing built-in array functions

  • 我们完全可以使用object来模拟数组,但是不推荐

Maps

  • 在以前,当我们要使用map这种数据结构的时候,object看起来是一个天然的map
    const dictionary = {
      "one": 1,
      "two": 2,
      "three": 3
    };
    
  • 这种"看起来没什么问题"的例子却隐藏着很多问题,比如我们试图访问dictionary的域 constructor,本来应该返回undefined,却返回了function值!
    const dictionary = {
      "one": 1,
      "two": 2,
      "three": 3
    };
    
    
    console.log(dictionary["constructor"]);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // [Function: Object]                             //
    ////////////////////////////////////////////////////
    
  • 原因很简单, 所有的objects都会有prototype, 刚好constructor是这个dictionary的 prototype里面"内置"的几个property
  • 使用object作为Map的另外一个问题,就是object的key,只能是string,所以有时候你的 key不是string的话,会强制调用toString(),这不是我们想要的

Creating our first map

  • 针对object作为map的各种弊端,我们的es6推出了一个新的内置的容器:map
    const ninjaIslandMap = new Map();
    
    const ninja1 = { name: "Yoshi"};
    const ninja2 = { name: "Hattori"};
    const ninja3 = { name: "Kuma"};
    
    ninjaIslandMap.set(ninja1, {homeIsland: "Honshu"});
    ninjaIslandMap.set(ninja2, {homeIsland: "Hokkaido"});
    
    console.log(ninjaIslandMap.get(ninja1));
    console.log(ninjaIslandMap.get(ninja2));
    
    
    console.log(ninjaIslandMap.has(ninja1));
    console.log(ninjaIslandMap.has(ninja3));
    console.log(ninjaIslandMap.size);
    ninjaIslandMap.delete(ninja1);
    console.log(ninjaIslandMap.size);
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // { homeIsland: 'Honshu' }                       //
    // { homeIsland: 'Hokkaido' }                     //
    // true                                           //
    // false                                          //
    // 2                                              //
    // 1                                              //
    ////////////////////////////////////////////////////
    

Iterating over maps

  • 我们可以使用所有对数组奏效的循环手段,外加map增加了两个函数:
    • keys()返回所有的key作为一个数组访问
    • values()返回所有的value作为一个数组访问
  • 例子如下
    const directory = new Map();
    
    directory.set("Yoshi", "+81 26 6462");
    directory.set("Kuma", "+81 522378 6462");
    directory.set("Hiro", "+81 76 277 46");
    
    for (let item of directory) {
      console.log(item);
    }
    
    for (let key of directory.keys()) {
      console.log(key);
    }
    
    for (let value of directory.values()) {
      console.log(value);
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // [ 'Yoshi', '+81 26 6462' ]                     //
    // [ 'Kuma', '+81 522378 6462' ]                  //
    // [ 'Hiro', '+81 76 277 46' ]                    //
    // Yoshi                                          //
    // Kuma                                           //
    // Hiro                                           //
    // +81 26 6462                                    //
    // +81 522378 6462                                //
    // +81 76 277 46                                  //
    ////////////////////////////////////////////////////
    

Sets

  • set 也是es6的新feature,使用方法和map也差不多
    const ninjas = new Set(["Kuma", "Hattori", "Yagyu", "Hattori"]);
    
    console.log(ninjas.has("Hattori"));
    console.log(ninjas.size);
    
    console.log(ninjas.has("YoShi"));
    ninjas.add("YoShi");
    console.log(ninjas.size);
    
    for (let ninja of ninjas) {
      console.log(ninja);
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // true                                           //
    // 3                                              //
    // false                                          //
    // 4                                              //
    // Kuma                                           //
    // Hattori                                        //
    // Yagyu                                          //
    // YoShi                                          //
    ////////////////////////////////////////////////////
    

Chapter 10: Wrangling regular expresions

  • 现在的程序开发完全离不开正则表达式,虽然可以避免使用正则表达式,但是很多长达半 页的代码可能只需要一行正则表达式来进行处理
  • 打开每一个js的库,你都会发现,在如下的几种任务中,正则表达式的应用最为流行:
    • 处理HTML nodes字符串
    • 在CSS selector表达式中定位某些selector
    • 确定某个element时候具有一个特定的classname
    • input validation

Why regular expressions rock

  • 先看一个正则表达式的例子,我们要验证用户在form里面的输入是不是符合US的九位邮 政编码.postal code(也叫ZIP code)的格式如下
    99999-9999
    
  • 我们先来看看,"半页"的很容易写出bug的代码
    function isThisAZipCode(candidate) {
      if (typeof candidate !== "string" ||
          candidate.length != 1) {
        return false;
      }
    
      for (let n = 0; n < candidate.length; n++) {
        let c = candidate[n];
        switch(n) {
        case 0: case 1: case 2: case 3: case 4:
        case 6: case 7: case 8: case 9:
          if ( c < '0' || c > '9') {
            return false;
          }
        case 5:
          if (c != '-') return false;
          break;
        }
      }
      return true;
    }
    
  • 然后我们再来看看我们的"一行"正则表达式. 高下立判
    function isThisAZipCode(candidate) {
      return /^\d{5}-\d{4}$/.test(candidate);
    }
    
  • TODO

Chapter 11: Code modularization techniques

  • 目前为止,我们已经了解了javascript的主要特性,比如function, object, collection, regular expression
  • 这些特性可以帮助我们解决一个特定的问题, 但是,当我们的代码急剧膨胀的时候,我们 迫切的需要一种方法来帮助我们组织管理代码.
  • 现代的代码管理系统已经证明,一个大的代码库不利于对代码的理解.所以推荐的方法是 把代码分成相对独立的模块(module),模块间的耦合要松
  • 如果把object和function看做是我们管理代码的小的单元的话,那么module就是一个比 较大的管理代码的单元
  • 如果你了解js的话,你会发现js原来在global vairable上面的使用太多了.每当我们在 全局使用var定义一个变量,或者在function里面忘记使用var来定义'局部变量'的时候, 总会产生一个全局变量
  • 全局变量在软件工程上面被看做是一个anti-pattern,因为代码库任何地方的代码都可 以去更改这个变量

Modularizing code in pre-ES6 JavaScript