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

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

函数是第一类对象

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

函数作为对象的乐趣

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

存储函数

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

记忆函数

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

函数定义

  • 函数声明

在声明前就可以被调用

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

必须先声明

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

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

(myArg) => {
return myArg*2
}

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

  • 立即函数

声明即调用

(function(){})(3)

函数的参数

剩余参数

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

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

默认参数

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

理解函数调用

隐含函数参数

函数调用时还会传递两个隐式的参数: arguments和this。

这些隐式参数在函数声明中没有明确定义, 但会默认传递给函数并

且可以在函数内正常访问。

arguments参数

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

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

this参数: 函数上下文

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

函数调用

四种调用函数的方法

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

    skulk.call(ninja)

1. 作为函数直接调用

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

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

2. 作为方法被调用

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

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

3. 作为构造函数调用

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

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

4. 使用apply和call方法调用

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

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

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

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

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

6. 使用bind函数

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

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

闭包和作用域

什么是闭包

闭包允许函数访问并操作函数外部的变量。只要变量或函数存在于

声明函数时的作用域内,闭包即可使函数能够访问这些变量或函数

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

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

使用闭包

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

封装私有变量

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

回调函数

处理回调函数是另一种常见的使用闭包的情景。 回调函数指的是需

要在将来不确定的某一时刻异步调用的函数。 通常,在这种回调函数

中, 我们经常需要频繁地访问外部数据。

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

未来的函数: 生成器和promise

使用生成器函数

生成器函数几乎是一个完全崭新的函数类型, 它和标准的普通函数

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

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

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

调用生成器函数不一定会执行生成器函数体。 通过创建迭代器对

象, 可以与生成器通信。

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

通过调用生成器得到的迭代器, 暴露出一个next方法能让我们向生

成器请求一个新值。 next方法返回一个携带着生成值的对象, 而该对象中包含的另一个属性done也向我们指示了生成器是否还会追加生成值。

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

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

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

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

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

生成器内部构成

  • 挂起开始——创建了一个生成器后, 它最先以这种状态开始。 其中

    的任何代码都未执行。
  • 执行——生成器中的代码执行的状态。 执行要么是刚开始, 要么是

    从上次挂起的时候继续的。 当生成器对应的迭代器调用了next方

    法, 并且当前存在可执行的代码时, 生成器都会转移到这个状态。
  • 挂起让渡——当生成器在执行过程中遇到了一个yield表达式, 它会

    创建一个包含着返回值的新对象, 随后再挂起执行。 生成器在这个

    状态暂停并等待继续执行。
  • 完成——在生成器执行期间, 如果代码执行到return语句或者全部代码执行完毕, 生成器就进入该状态

使用promise

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

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

resolve, reject

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

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

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

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

把生成器和promise相结合

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

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

async函数获取了一个生成器, 调用它并创建了一个迭代器用来恢

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

实现同样功能的async函数

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

(async function () {
try {
const ninjas = await getJSON("data/ninjas.json");
const missions = await getJSON(missions[0].missionsUrl);
console.log(missions);
}c
atch(e){
console.log("Error: ", e);
}
})()

读Secrets of the JavaScript Ninja(一)函数的更多相关文章

  1. 读Secrets of the JavaScript Ninja(二)对象

    面向对象和原型 理解原型 在JavaScript中,可通过原型实现继承.原型的概念很简单.每个对象都含有原型的引用,当查找属性时,若对象本身不具有该属性,则会查找原型上是否有该属性. 每个对象都可以有 ...

  2. [在读]Secrets of the javascript Ninja

    很棒的一本,可惜没有中文版.

  3. 《Secrets of the JavaScript Ninja》:JavaScript 之运行时代码

    最近,在阅读 jQuery 之父 John Resig 力作:Secrets of the JavaScript Ninja(JavaScript忍者秘籍).关于第九章提及的 JavaScript 之 ...

  4. 《JavaScript Ninja》之函数是根基

    函数是根基 理解函数为什么如此重要 JavaScript 是一门 函数式语言 . 函数为什么是第一型对象 在 JavaScript 中,函数可以共处,可以将其视为其他任意类型的 JavaScript ...

  5. 深入理解JavaScript中的函数操作——《JavaScript忍者秘籍》总结

    匿名函数 对于什么是匿名函数,这里就不做过多介绍了.我们需要知道的是,对于JavaScript而言,匿名函数是一个很重要且具有逻辑性的特性.通常,匿名函数的使用情况是:创建一个供以后使用的函数.简单的 ...

  6. 【JavaScript】Javascript中的函数声明和函数表达式

    Javascript有很多有趣的用法,在Google Code Search里能找到不少,举一个例子: <script> ~function() { alert("hello, ...

  7. 浅析 JavaScript 中的 函数 uncurrying 反柯里化

    柯里化 柯里化又称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果. 因此柯里化的过程是 ...

  8. 02、Java的lambda表达式和JavaScript的箭头函数

    前言 在JDK8和ES6的语言发展中,在Java的lambda表达式和JavaScript的箭头函数这两者有着千丝万缕的联系:本次试图通过这篇文章弄懂上面的两个"语法糖". 简介 ...

  9. JavaScript权威指南 - 函数

    函数本身就是一段JavaScript代码,定义一次但可能被调用任意次.如果函数挂载在一个对象上,作为对象的一个属性,通常这种函数被称作对象的方法.用于初始化一个新创建的对象的函数被称作构造函数. 相对 ...

随机推荐

  1. 输入一个表示整数的字符串,把该字符串转换成整数并输出(实现atoi函数功能)

    例如输入字符串"345",则输出整数345.-----------------------------此题一点也不简单.不信,你就先不看一下的代码,你自己先写一份,然后再对比一下, ...

  2. OpenCV应用(3) 简单轮廓匹配的小例子

    具体应用 https://blog.csdn.net/kyjl888/article/details/85060883 OpenCV中提供了几个与轮廓相关的函数: findContours():从二值 ...

  3. RPC笔记搬迁

      选择dubbo 启动原理 解析服务 暴露服务 引用服务 提供服务流程 结合Netty 对比 HSF   https://www.cnblogs.com/lichengwei/p/5529492.h ...

  4. Game of Cards Gym - 101128G (SG函数)

    Problem G: Game of Cards \[ Time Limit: 1 s \quad Memory Limit: 256 MiB \] 题意 题意就是给出\(n\)堆扑克牌,然后给出一个 ...

  5. TCP三次握手的过程,accept发生在三次握手的哪一个阶段?

    答案是:accept过程发生在三次握手之后,三次握手完成后,客户端和服务器就建立了tcp连接并可以进行数据交互了.这时可以调用accept函数获得此连接. TCP Accept总结 TCP Accep ...

  6. cf1189解题报告

    cf1189div2解题报告 codeforces A 答案要不是一串要不就是去掉最后一个字母的两串 #include <bits/stdc++.h> #define ll long lo ...

  7. access us

    Ubuntu下设置 chrome的SwitchyOmega Wiki (简体中文)wiki Linux安装配置客户端及开机自动启动 运维 安装 配置 搭建服务以及配置多用户 安装和配置 一键搭建 服务 ...

  8. (ACP)敏捷项目管理

    第1章 为什么需要敏捷 第2章 敏捷和敏捷项目管理定义 第3章 敏捷项目管理价值和原则 1.我们的最高目标是,通过尽早持续交付有价值的软件来满足客户的需求 2.欢迎对需求提出变更,即使在项目开发后期也 ...

  9. python发送钉钉机器人脚本

    #!/usr/bin/python# -*- coding: utf-8 -*-import requestsimport jsonimport sysimport os headers = {'Co ...

  10. HashMap多线程并发问题分析-正常和异常的rehash1(阿里)

    多线程put后可能导致get死循环 从前我们的Java代码因为一些原因使用了HashMap这个东西,但是当时的程序是单线程的,一切都没有问题.后来,我们的程序性能有问题,所以需要变成多线程的,于是,变 ...