理解JavaScript为什么应该作为函数式
在JavaScript中,函数是程序执行过程中的主要模块单元
函数是第一类对象
- 通过字面量创建
function ninjaFunction(){}
- 赋值给变量,数组项或其它对象的属性
var ninjaFunction = function() {}
ninjaFunction.push(function(){})
ninja.data = function(){}
- 作为函数参数来传递
call(function(){})
- 作为函数的返回值
function returnNewNinjaFunction() {
return function() {}
}
- 具有动态创建和分配的属性
var ninjaFunction = function() {}
ninjaFunction.ninja = "Hanzo"
函数作为对象的乐趣
通过向函数添加属性来实现存储函数和自记忆函数
存储函数
var store = {
nextId: 1,
cache: {},
add: function(fn) {
if (!fn.id) {
fn.id = this.nextId++;
this.cache[fn.id] = fn;
return true;
}}
};
记忆函数
function isPrime(value) {
if (!isPrime.answers) {
isPrime.answers = {};
}
if (isPrime.answers[value] !== undefined) {
return isPrime.answers[value];
}
var prime = value !== 0 && value !== 1;
for (var i = 2; i < value; i++) {
if (value % i === 0) {
prime = false;
break;
}
}
return isPrime.answers[value] = prime;
}
函数定义
- 函数声明
在声明前就可以被调用
function myFun(){ return 1;}
- 函数表达式
必须先声明
const func = function() {}
- 箭头函数
函数内的this从上一层作用域继承
(myArg) => {
return myArg*2
}
如果括号里只有一个参数就可以省略括号,箭头后只有一条语句可以省略大括号,作为这个箭头函数的返回值
- 立即函数
声明即调用
(function(){})(3)
函数的参数
剩余参数
剩余参数以...作为前缀声明,以数组形式传入函数
function multiMax(first, ...remainingNumbers) {
var sorted = remainingNumbers.sort(function(a, b) {
return b – a;
});
return first * sorted[0];
}
默认参数
function performAction(ninja, action = "skulking") {
return ninja + " " + action;
}
理解函数调用
隐含函数参数
函数调用时还会传递两个隐式的参数: arguments和this。
这些隐式参数在函数声明中没有明确定义, 但会默认传递给函数并
且可以在函数内正常访问。
arguments参数
arguments可以访问到传给函数所有的参数,虽然arguments具有Length属性,但是它只是一个类数组结构,并不是数组
- 操作所有参数
function sum() {
var sum = 0;
for(var i = 0; i < arguments.length; i++){
sum += arguments[i];
}
return sum;
}
this参数: 函数上下文
this参数的指向不仅是由定义函数的方式和位置决定的, 同时还严重受到函数调用方式的影响。
函数调用
四种调用函数的方法
- 作为一个函数(function)——skulk(), 直接被调用。
- 作为一个方法(method)——ninja.skulk(), 关联在一个对象上, 实现面向对象编程。
- 作为一个构造函数(constructor)——new Ninja(), 实例化一个新的对象。
- 通过函数的apply或者call方法——skulk.apply(ninja)或者
skulk.call(ninja)
1. 作为函数直接调用
也就是在全局环境中直接调用,在严格模式下,函数内部的this应该是undefined,非严格模式下就是window
function ninja() {};
ninja();
var samurai = function(){};
samurai();
(function(){})()
2. 作为方法被调用
当作为方法被调用时,函数内部的this应该为调用这个函数的对象
var ninja = {};
ninja.skulk = function(){};
ninja.skulk();
3. 作为构造函数调用
当函数时候new关键词作为构造函数来调用时,会发生三个动作
- 创建一个空对象
- 把这个空对象作为当前函数的this
- 新构造的对象作为new的返回值
function Ninja() {
this.skulk = function() {
return this;
};
}
var ninja1 = new Ninja();
var ninja2 = new Ninja();
4. 使用apply和call方法调用
JavaScript为我们提供了一种调用函数的方式, 从而可以显式地指定任何对象作为函数的上下文。 apply、call是函数的方法,即函数在JavaScript中也是对象
function juggle() {
var result = 0;
for (var n = 0; n < arguments.length; n++) {
result += arguments[n];
}
this.result = result;
}
var ninja1 = {};
var ninja2 = {};
juggle.apply(ninja1,[1,2,3,4]);
juggle.call(ninja2, 5,6,7,8);
assert(ninja1.result === 10, "juggled via apply");
assert(ninja2.result === 26, "juggled via call");
5. 使用箭头函数绕过函数上下文
箭头函数的this也就是函数上下文是从它的上层作用域继承来的
this.click = () => {
this.clicked = true;
};
6. 使用bind函数
bind也是函数原型的一个方法, 为一个函数绑定它的上下文
var boundFunction = button.click.bind(button);
闭包和作用域
什么是闭包
闭包允许函数访问并操作函数外部的变量。只要变量或函数存在于
声明函数时的作用域内,闭包即可使函数能够访问这些变量或函数
var outerValue = "samurai";
var later;
function outerFunction() {
var innerValue = "ninja";
function innerFunction() {
assert(outerValue === "samurai", "I can see the samurai.");
assert(innerValue === "ninja", "I can see the ninja.")
}
later = innerFunction;
}
outerFunction();
later();
当在外部函数中声明内部函数时, 不仅定义了函数的声明, 而且还创建了一个闭包。 该闭包不仅包含了函数的声明, 还包含了在函数声明时该作用域中的所有变量。 当最终执行内部函数时, 尽管声明时的作用域已经消失了, 但是通过闭包, 仍然能够访问到原始作用域
使用闭包
通过构造函数创建了一个含有两个方法getFeints,feint的对象,函数内部的feints变量只有通过getFeints实现了闭包才能访问,从而实现了封装私有变量
封装私有变量
function Ninja() {
var feints = 0;
this.getFeints = function() {
return feints;
};
this.feint = function() {
feints++;
};
}
var ninja1 = new Ninja();
ninja1.feint();
assert(ninja1.feints === undefined,
"And the private data is inaccessible to us.");
assert(ninja1.getFeints() === 1,
"We're able to access the internal feint count.");
var ninja2 = new Ninja();
assert(ninja2.getFeints() === 0,
"The second ninja object gets its own feints variable.");
回调函数
处理回调函数是另一种常见的使用闭包的情景。 回调函数指的是需
要在将来不确定的某一时刻异步调用的函数。 通常,在这种回调函数
中, 我们经常需要频繁地访问外部数据。
function animateIt(elementId) {
var elem = document.getElementById(elementId);
var tick = 0;
var timer = setInterval(function() {
if (tick < 100) {
elem.style.left = elem.style.top = tick + "px";
tick++;
}
else {
clearInterval(timer);
assert(tick === 100,
"Tick accessed via a closure.");
assert(elem,
"Element also accessed via a closure.");
assert(timer,
"Timer reference also obtained via a closure.");
}
}, 10);
}
animateIt("box1");
未来的函数: 生成器和promise
使用生成器函数
生成器函数几乎是一个完全崭新的函数类型, 它和标准的普通函数
完全不同。生成器(generator) 函数能生成一组值的序列, 但每个值的生成是基于每次请求, 并不同于标准函数那样立即生成。 我们必须显式地向生成器请求一个新的值, 随后生成器要么响应一个新生成的值, 要么就告诉我们它之后都不会再生成新值。 更让人好奇的是, 每当生成器函数生成了一个值, 它都不会像普通函数一样停止执行。 相反, 生成器几乎从不挂起。 随后, 当对另一个值的请求到来后, 生成器就会从上次离开的位置恢复执行。
function* WeaponGenerator() {
yield "Katana";
yield "Wakizashi";
yield "Kusarigama";
}
for (let weapon of WeaponGenerator()) {
assert(weapon !== undefined, weapon);
}
通过迭代器对象控制生成器
调用生成器函数不一定会执行生成器函数体。 通过创建迭代器对
象, 可以与生成器通信。
function* WeaponGenerator() {
yield "Katana";
yield "Wakizashi";
}
const weaponsIterator = WeaponGenerator();
const result1 = weaponsIterator.next();
assert(typeof result1 === "object"
&& result1.value === "Katana"
&& !result1.done,
"Katana received!");
const result2 = weaponsIterator.next();
assert(typeof result2 === "object"
&& result2.value === "Wakizashi"
&& !result2.done,
"Wakizashi received!");
const result3 = weaponsIterator.next();
assert(typeof result3 === "object"
&& result3.value === undefined
&& result3.done,
"There are no more results!");
通过调用生成器得到的迭代器, 暴露出一个next方法能让我们向生
成器请求一个新值。 next方法返回一个携带着生成值的对象, 而该对象中包含的另一个属性done也向我们指示了生成器是否还会追加生成值。
function* WeaponGenerator(){
yield "Katana";
yield "Wakizashi";
}
const weaponsIterator = WeaponGenerator();
let item;
while(!(item = weaponsIterator.next()).done) {
assert(item !== null, item.value);
}
把执行权交给下一个生成器
function* WarriorGenerator(){
yield "Sun Tzu";
yield* NinjaGenerator();
yield "Genghis Khan";
}
function* NinjaGenerator(){
yield "Hattori";
yield "Yoshi";
}
for(let warrior of WarriorGenerator()){
assert(warrior !== null, warrior);
}
作为生成器函数参数发送值
function* NinjaGenerator(action) {
const imposter = yield ("Hattori " + action);
assert(imposter === "Hanzo",
"The generator has been infiltrated");
yield ("Yoshi (" + imposter + ") " + action);
}
const ninjaIterator = NinjaGenerator("skulk");
const result1 = ninjaIterator.next();
assert(result1.value === "Hattori skulk","Hattori is skulking");
const result2 = ninjaIterator.next("Hanzo");
assert(result2.value === "Yoshi (Hanzo) skulk",
"We have an imposter!");
生成器内部构成
- 挂起开始——创建了一个生成器后, 它最先以这种状态开始。 其中
的任何代码都未执行。 - 执行——生成器中的代码执行的状态。 执行要么是刚开始, 要么是
从上次挂起的时候继续的。 当生成器对应的迭代器调用了next方
法, 并且当前存在可执行的代码时, 生成器都会转移到这个状态。 - 挂起让渡——当生成器在执行过程中遇到了一个yield表达式, 它会
创建一个包含着返回值的新对象, 随后再挂起执行。 生成器在这个
状态暂停并等待继续执行。 - 完成——在生成器执行期间, 如果代码执行到return语句或者全部代码执行完毕, 生成器就进入该状态
使用promise
promise是ES6中引入来解决异步人物的一个方案,promise对象是对我们现在尚未得到但将来会得到值的占位符;它是对我们最终能够得知异步计算结果的一种保证。
const ninjaPromise = new Promise((resolve, reject) => {
resolve("Hattori");
//reject("An error resolving a promise!");
});
ninjaPromise.then(ninja => {
assert(ninja === "Hattori", "We were promised Hattori!");
},err => {
fail("There shouldn't be an error")
});
resolve, reject
resolve为异步任务成功后调用下一个then
const ninjaImmediatePromise = new Promise((resolve, reject) => {
report("ninjaImmediatePromise executor. Immediate resolve.");
resolve("Yoshi");
});
ninjaImmediatePromise.then(ninja => {
assert(ninja === "Yoshi",
"ninjaImmediatePromise resolve handled with Yoshi");
});
reject为失败抛出异常,由catch接住
const promise = new Promise((resolve, reject) => {
reject("Explicitly reject a promise!");
});
promise.then(() => fail("Happy path, won't be called!"), error => pass("A promise was explicitly rejected!")
);
把生成器和promise相结合
将生成器和promise结合, 从而以优雅的同步代码方式完成异步任务
async(function*(){
try {
const ninjas = yield getJSON("data/ninjas.json");
const missions = yield getJSON(ninjas[0].missionsUrl);
const missionDescription = yield getJSON(missions[0].detailsUrl);
}
catch(e) {
}
});
function async(generator) {
var iterator = generator();
function handle(iteratorResult) {
if(iteratorResult.done) { return; }
const iteratorValue = iteratorResult.value;
if(iteratorValue instanceof Promise) {
iteratorValue.then(res => handle(iterator.next(res)))
.catch(err => iterator.throw(err));
}
}
try {
handle(iterator.next());
}
catch (e) { iterator.throw(e); }
}
async函数获取了一个生成器, 调用它并创建了一个迭代器用来恢
复生成器的执行。 在async函数内, 我们声明了一个handle函数用于处理从生成器中返回的值——迭代器的一次“迭代”。 如果生成器的结果是一个被成功兑现的承诺,我们就是用迭代器的next方法把承诺的值返回给生成器并恢复执行。如果出现错误, 承诺被违背, 我们就使用迭代器的throw方法(告诉过你迟早能派上用场了) 抛出一个异常。 直到生成器的工作完成前, 我们都会一直重复这几个操作
实现同样功能的async函数
通过在关键字function之前使用关键字async, 可以表明当前的函数依赖一个异步返回的值。 在每个调用异步任务的位置上, 都要放置一个await关键字, 用来告诉JavaScript引擎, 请在不阻塞应用执行的情况下在这个位置上等待执行结果。
(async function () {
try {
const ninjas = await getJSON("data/ninjas.json");
const missions = await getJSON(missions[0].missionsUrl);
console.log(missions);
}c
atch(e){
console.log("Error: ", e);
}
})()