读Secrets of the JavaScript Ninja(一)函数

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

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

函数是第一类对象

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

函数作为对象的乐趣

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

存储函数

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

记忆函数

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

函数定义

  • 函数声明

在声明前就可以被调用

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

必须先声明

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

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

(myArg) => {
  return myArg*2
}

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

  • 立即函数

声明即调用

(function(){})(3)

函数的参数

剩余参数

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

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

默认参数

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

理解函数调用

隐含函数参数

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

arguments参数

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

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

this参数: 函数上下文

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

函数调用

四种调用函数的方法

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

1. 作为函数直接调用

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

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

2. 作为方法被调用

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

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

3. 作为构造函数调用

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

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

4. 使用apply和call方法调用

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

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

assert(ninja1.result === 10, "juggled via apply");
assert(ninja2.result === 26, "juggled via call");

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

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

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

6. 使用bind函数

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

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

闭包和作用域

什么是闭包

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

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

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

使用闭包

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

封装私有变量

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

回调函数

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

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

未来的函数: 生成器和promise

使用生成器函数

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

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

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

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

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

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

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

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

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

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

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

生成器内部构成

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

使用promise

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

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

resolve, reject

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

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

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

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

把生成器和promise相结合

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

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

  try {
    handle(iterator.next());
  }
  catch (e) { iterator.throw(e); }  
}

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

实现同样功能的async函数

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

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

转载于:https://www.cnblogs.com/secoding/p/11160908.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值