1、函数也可以添加属性
利用计算素数的例子来说明:可以通过函数的属性来缓存结果,提升性能。
这里是函数的属性,用this的话会绑定到window上,函数创建默认this为window
function isPrime(value) {
if (!isPrime.answers) {
isPrime.answers = {
};
}
if (isPrime.answers[value] !== undefined) {
return isPrime.answers[value];
}
var prime = value !== 0 && value !== 1; // 1 is not a 素数
for (var i = 2; i < value; i++) {
if (value % i === 0) {
prime = false;
break;
}
}
return isPrime.answers[value] = prime;
}
2、var、let、const
var 是在距离最近的函数或全局词法环境中定义变量。
let 和 const 在最近的词法环境中定义变量(可以是在块级作用域内、循环内、函数内或全局环境内,循环其实就是一个块级作用域)
3、js代码的执行过程
代码运行的两个阶段:
一旦创建了新的词法环境
执行第一阶段:不执行代码,扫描所有代码
JavaScript 引擎会访问并注册在当前词法环境中所声明的变量和函数。
具体的处理过程如下:
1.先创建形参与参数默认值:如果是创建一个函数环境,那么创建形参及函数参数的默认值。非函数环境跳过此步骤。
2.声明函数:扫描当前代码进行函数声明。
1)如果是创建全局或函数环境,就扫描当前代码进行函数声明(不会扫描其他函数的函数体),但是不会执行函数表达式或箭头函数。
2)对于所找到的函数声明,将创建函数,并绑定到当前环境与函数名相同的标识符上。若该标识符已经存在,那么该标识符的值将被重写(同名函数被覆盖)。如果是块级作用域,将跳过此步骤。
3.声明变量:扫描当前代码进行变量声明。
1)在函数或全局环境中,查找所有当前函数以及其他函数之外(就是不在函数体里的)通过 var 声明的变量,并查找所有通过 let 或 const 定义的变量。
2)在块级环境中,仅查找当前块中通过 let 或 const 定义的变量。
3)对于所查找到的变量,若该标识符不存在,进行注册并将其初始化为 undefined。若该标识符已经存在,将保留其值(这就是为什么变量覆盖不了函数声明,因为函数声明已经占用了这个标识符)。
第二阶段:执行代码
具体如何执行取决于变量的类型(let、var、const 和函数声明)以及环境类型(全局环境、函数环境或块级作用域)
在程序的实际执行过程中,跳过了函数声明部分,而不是简单的认为把它提升了
补充知识:
1)无论何时创建函数,都会保持词法环境的引用(通过内置[[Environment]]属性)
2)每次调用函数时均会创建新的执行上下文并推入执行栈。这同时引起创建新的词法环境,词法环境通常用于保持跟踪函数中定义的变量。
3)this 表示函数上下文,即与函数调用相关联的对象。函数的定义方式和调用方式决定了 this 的取值。
4、生成器与迭代器
生成器四个状态:
● 挂起开始——创建了一个生成器,其中的任何代码都未执行。
● 执行——生成器中的代码已执行。执行要么是刚开始,要么是从上次挂起的时
候继续的。当生成器对应的迭代器调用了 next 方法,并且当前存在可执行的代
码时,生成器都会转移到这个状态。
● 挂起让渡——当生成器在执行过程中遇到了一个 yield 表达式,它会创建一个包
含着返回值的新对象,随后再挂起执行。生成器在这个状态暂停并等待继续执行。
● 完成——在生成器执行期间,如果代码执行到 return 语句或者全部代码执行完
毕,生成器就进入该状态。
特点:当我们从生成器中取得控制权后,生成器的执行环境上下文一直是保存的,而不是像标准函数一样退出后销毁
1)生成器定义
function* WeaponGenerator() {
yield "Katana";
yield "Wakizashi";
yield "Kusarigama";
}
// 生成器执行 就等于生成了一个 迭代器
WeaponGenerator()
2)for of循环可以取出每一步运行的值
for (let weapon of WeaponGenerator()) {
console.log(weapon)
}
3)for of其实就是语法糖
const weaponsIterator = WeaponGenerator()
let item
while(!(item = weaponsIterator.next()).done) {
console.log(item)
}
4)生成器与递归遍历dom节点
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)) {
console.log(element)
}
5)向生成器传值:
迭代器.next(xxxx) 这里的xxx参数就是生成器中,当前挂起的yield后表达式的返回值,
当然不能通过这样初始化,初始化可以直接在声明迭代器的时候传参
function* NinjaGenerator(action) {
const imposter = yield ("Hattori " + action);
assert(imposter === "Hanzo", "The generator has been infiltrated");
yield ("Yoshi (" + imposter + ") " + action);
}
const ninjaIterator = NinjaGenerator("skulk"); // 初始化传值
6)向生成器抛出异常(也是种传值)
function* NinjaGenerator() {
try{
yield "Hattori";
fail("The expected exception didn't occur"); // 这里的代码不会执行
}
catch(e){
assert(e === "Catch this!", "Aha! We caught an exception");
}
}
const ninjaIterator = NinjaGenerator();
const result1 = ninjaIterator.next();
assert(result1.value === "Hattori", "We got Hattori");
ninjaIterator.throw("Catch this!");
7)next方法不会创建新的执行上下文到执行栈,而是激活当前的生成器执行上下文
const result1 = ninjaIterator.next();
习题中的反省:
1)for of中获取生成器的值时,return的值不会被使用,详情见上方while循环代码,因为判断出当前的状态是done时,就已经停止了,不会运行循环中的处理函数。
下面代码只会push第一个个值
function* NinjaGenerator(){
yield "Yoshi";
return "Hattori";
yield "Hanzo";
}
var ninjas = [];
for(let ninja of NinjaGenerator()){
ninjas.push(ninja);
}
2)当前.next()中转入的参数是上一个yield的返回值
function* Gen(val) {
val = yield val * 2;
yield val;
}
let generator = Gen(2);
let a1 = generator.next(3).value; // 4 这里传入的3会被忽略
let a2 = generator.next(5).value; // 是5 !!!!!!!!!!!!!!!!!!!!!!!!!!!!
5、Promise
可以使用替代预发来处理拒绝 promise,通过使用内置的 catch 方法
promise.then(()=> fail("Happy path, won't be called!"))
.catch(() => pass("Promise was also rejected"));
//then里面只写了resolve后的执行方法,链式调用.catch来处理错误
如果在处理promise时出现末处理的异常,则会被隐式地拒绝,catch和error回调都可以捕获到
const promise = new Promise((resolve, reject) => {
undeclaredVariable++; // 未定义变量
});
promise.then(() => fail("Happy path, won't be called!"))
.catch(error => pass(