secrets-of-the-javascript-ninja-2nd
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?
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:
- 分析HTML,并且建设好Document Object Model(DOM)
- 执行JS 代码
- 简单点说,step1就是分析html element代码(除了script的部分),而step2就是分析script
的部分.这两个部分的分析可以交替进行,并不是严格的一个接着一个
Parsing the HTML and building the DOM
Executing JavaScript Code
- 所有html里面的script element都是由浏览器的javascript engine来执行的,这些引
擎最出名的就是google的V8了,同时还有firefox的Spidermonkey
- js和浏览器交互的方法是: 浏览器通过'global object'提供了一些API,让js来和page
进行互动
GLOBAL OBJECTS IN JAVASCRIPT
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
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的方式创建
- 可以被赋值给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
的一部分,它总是会把:
- 自己放到一个变量里面.
var doNothing = function() {};
doNothing();
- 或者赋予一个参数, 其实也是变相的赋值给参数:
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设计了一种新的格式,其格式为
- 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, 这种情况下又有两说:
- 老的js实现,也就是none-strict mode下面,我们的this会指向window,这是非常
不好的实现,因为我们调用function'没指定object',你却默认给我一个object,
这首先不合理.其实我们后面会看到,这种实现会'污染global namespace'
- 新的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内部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
Fixing the problem of function contexts
Using arrow functions to get around function contexts
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
Putting closures to work
- 我们已经从概念上了解了closure,下面来看看closure如何在js代码中起作用的例子
Mimicking private variables
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实现了!
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中,我们可以使用三种方法定义变量:
- 它们的区别在"是否可变"以及和lexical environment的关系不同
Variable mutability
- 那就是分成:
- 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的指针
扫描并注册的时候,但是注意如下两点:
- arrow function不会被注意到!
- 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
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
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
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, 有两个参数(其实也是两个函数):
- 这个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函数早一点
罢了
Creating our firs real-world promise
Waiting for a number of promises
Combining generators and promises
Chapter 7: Object orientation with prototypes
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里面
呢?
- 结果恰恰相反:
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语言的机制涉及到两个变量:
- 所以我们要"手动"设置这两个变量,代码看起来就非常的麻烦
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,我们可以使用最简单的代码,如下
- 上面的做法过于的随意,所以我们希望能够做到如下的'限制':
- 我们希望能够正确的设置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的访问", 包括:
- 我们先来看看一个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 proxies for measuring performance
Using proxies to autopopulate properties
Using proxies to implement negative array Indexes
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