你不知道的Javascript(下卷)笔记
深入编程
Javascript 是解释型的,实际上是动态编译程序,然后立即执行编译后的代码。
编写代码是为了给开发者阅读的,代码应该解释为什么,而非是什么。
深入 Javascript
Javascript 中”假”值的列表:
- “” (空字符串)
- 0, -0, NaN (无效数字)
- null、undefined
- false
任何不在”假”值列表中的值都是”真”值,例如[],{}等。
在Jvascript中,闭包最常见的应用是模块模式。模块模式允许你定义外部不可见的私有实现细节(变量、函数),同时也可以提供允许从外部访问的公开API。
ES6及更新版本
transpiling:通过transpiling(transformation+compiling,转换+编译)的技术,将ES6代码转化为等价(或近似)的可以在ES5环境下工作的代码。
let+for
1 |
var funcs = []; |
for循环头部的let i 不只是为for循环本身声明了一个i,而是为循环的每一次迭代都重新声明了一个新的i。这意味着loop迭代内部创建的闭包封闭的是每次迭代中的变量。
spread/rest
ES6引入的新的运算符…,通常称为spread/rest运算符,取决于在哪/如何被使用。
1 |
// spread |
1 |
// rest |
这种用法最好的一点是,为类数组 arguments 提供了非常可靠的替代形式。
箭头函数
箭头函数总是函数表达式,并不存在箭头函数声明,箭头函数是匿名函数表达式,它们没有用于递归或者事件绑定/解绑定的命名引用。
箭头函数的主要设计目的是以特定的方式改变this的行为特性。
1 |
var controller = { |
因为 this 是动态的,通过变量 self 依赖于词法作用域的可预测性。在箭头函数内部,this 绑定不是动态的,是词法的。这是箭头函数的主要设计特性。
除了词法 this,箭头函数还有词法 arguments,它们没有自己的 arguments 数组,而是继承自父层–词法 super 和 new.target 也是一样。
代码组织
模块
- ES6 使用基于文件的模块,也就是一个文件一个模块。
- ES6 模块的 API 是静态的。也就是说,需要在模块的公开 API 中静态定义所有最高层导出,之后无法补充。
- ES6 模块是单例。
- 模块的公开 API 暴露的属性和方法并不仅仅是普通的值或引用的赋值。他们是到内部模块定义中的标识符的实际绑定(几乎类似于指针)。
- 导入模块和静态请求加载(如果还没加载的话)这个模块是一样的。
import 和 export 都必须出现在使用它们的最顶层作用域。举例来说,不能把 import 或 export 放在 if 条件中,它必须出现在所有代码块和函数的外面。
比较下面两段代码:
1 |
function foo() { |
1 |
function foo() { |
第一段代码中,导出的是此刻到函数表达式的绑定,而不是标识符foo,也就是说,export default… 接受的是一个表达式,如果之后 foo 赋了一个不同的值,模块导入得到的仍然是原来导出的函数,而不是新的值。
第二段代码,默认导出绑定实际上绑定到 foo 标识符而不是它的值,所以如果之后修改了 foo 的值,导入的值也会更新。
模块依赖环
import 语句使用外部环境(浏览器、Node.js等)提供的独立机制,来实际把模块标识符字符串解析成可用的指令,用于寻找和加载所需的模块,这个机制就是系统模块加载器。浏览器中环境提供的模块加载器会把模块标识符解析为URL,在Node.js这种服务器上就解析为本地文件系统路径。
类
// TODO
笔记-你不知道的JS(下卷)(未完)
第一部分 JS基础
第一章与第二章 基础
1.内置类型:string、number、boolean、null&undefined、object、symbol(ES6),object子类型:object(一般)、array、function、Date。推荐一张思维导图。
2.typeof:用于检查值的类型,而不是变量的类型。
typeof "string": "string"
typeof 1 : "number" typeof NAN : number
typeof true : "boolean"
typeof null : "object"
typeof {} : "object"
typeof [] : "object"
typeof function(){} : "function"
3.==与===:==进行类型转换,检查值是否相等;===检查类型和值是否都相同;注意对象之间比较的是引用,引用地址不等时,==与===效果一样。
"1" === "1" //true
"1" === 1 //false
"1" == 1 //true
new String("1") == new String("1") //false
注:==只触发隐式类型转换,与装箱(封箱)无关。
4.装箱(封箱):string对应String,number对应Number,boolean对应Boolean。在调用内置类型方法时,就会自动触发封箱过程,如"1".length、"a".toUpperCase、(3.1415).toFixed(2) 等等。
typeof "1" //"string"
typeof new String("1") //"object"
5.var vs let:此处简述主要区别,var函数级作用域(存在函数内作用域提升),let块级作用域(常区别于:if、for、while等块逻辑)。
6.let vs const:此处简述,了解c++指针对此处理解非常简单,当存在变更地址的逻辑时:无论时简单类型(string、number、boolean),还是对象类型(object),只能用let;当只变更值时,简单类型(string、number、boolean)只能用let,对象类型(object),推荐用const(加快编译,当然也可用let);当值和地址都不变时,上述类型都推荐用const。
7.IIFE(立即调用函数表达式):主要应用于,创建新的变量作用域,保护外部作用域不被污染。其它应用都是无用炫技,但是在let面前这些都是花里胡哨。
8.闭包与作用域(参见上卷笔记):简单提一句:在声明时可访问的变量,在执行时也可访问。
9.this与原型(参见上卷笔记)
10.polyfill vs transpile:polyfill相当于打补丁,当代码出现利用ES6特性时,通过增加函数的方式来保证逻辑正常运行,常用ES6-Shim;transpile相当于转译,把遵循ES6(或后续ES7等)写的代码转译成ES5,比如Babel。这两者都是为了旧版浏览器能运行那些遵循新规则的JS代码。
11.非JS:DOM API(如getElementById),BOM API(如alert、prompt、console.log),此处推荐文章:BOM和DOM详解。
第三章
摘抄三勺鸡汤
1.学习其它语言的认真的开发者会想要花费精力学习他们使用的语言的方方面面,但JS开发者却通常不会学习这门语言的很多内容(原书的翻译)。(这个的原因有一部分是因为很多开发者都是转型过来,原先做桌面的,做后端的,为了吃饭,转前端了,觉得前端是脚本,没那么复杂,用到哪学到哪,故不会系统且全面的进行学习。70-80的程序员大都如此。)
2.本书的宗旨是,所有JS开发者都可以,也应该学习这门伟大的语言的方方面面。个人偏见、框架假定(?)和项目的截止日期都不应该成为你从不学习和深入理解JS的借口。(Flag)
3.“你不知道的JS”既不是批判也不是攻击,这是一种领悟。(多么痛的领悟)
第二部分 ES6新增
第一章及第二章 语法
1.再谈块作用域 let:
//第一种显示声明
{
let a;
}
//第二种(目前只在for语句出现)
let(..){..}
重要:for循环中let i,不是为整个for循环声明一个变量i,而是为每一次迭代声明一个独立的变量i。
考虑如下代码:
//let 块作用域
var funcs = [];
for (let i=0; i<5; i++){
funcs.push( function(){
console.log(i);
});
}
funcs[3](); // 3
//var 函数作用域
var funcs = [];
for (var i=0; i<5; i++){
funcs.push( function(){
console.log(i);
});
}
funcs[3](); // 5
2.spread/rest:常用于形参与实参的传参过程。
function foo(x,y,z){
console.log(x,y,z);
}
foo( ...[1,2,3] ) // 1,2,3
function foo(x,y,...z){
console.log(x,y,z);
}
foo(1,2,3,4,5) // 1,2, [3,4,5]
用于实参数组时,展开数组传参;用于形参时,实参对应剩余部分归集为一数组。用于形参时,...args又称为rest参数,可替换原有繁琐的arguments类数组对象。
3.默认参数值:
function foo(x = 1,y = 2){
console.log(x,y);
}
foo() //1,2
foo(5) //5,2
foo(5, undefined) //5,2
foo(5, null) //5,0 null强制转换为0
4.解构
个人意见除了在import中习惯使用,其余时候基本就是用来缩减一些临时变量的代码,总的来说花里胡哨,包括下一节的简洁属性和简洁方法,老老实实写就ok了,对应上年纪的或者已经习惯旧有写法的人来说,花在这上面熟悉新写法,不如把脑袋里内存用于其它更值得的地方,此处性价比不高,过(鉴定完毕,莫抬杠)。
5.模板字面量:
let name = "sz";
let greeting = `hello ${name}`; //hello sz
//在模板中可调用函数
function upper(s){
s.toUpperCase();
}
let greeting = `hello ${upper(name)}`; //hello SZ
let more = `hello ${upper(`dear ${name}`)}`; //hello DEAR SZ
//raw 原始字符串
console.log(`Hello\nWorld`); //Hello 换行 World
console.log(String.raw`Hello\nWorld`); //Hello\nWorld
6.箭头函数 =>
重要:箭头函数不只是更短的语法,而是bind(this)。
非常重要:this在非箭头函数里时动态的,即看调用者是谁;this在箭头函数中是词法的,即词法作用域下的this(一开始较难理解),可简单理解为:取决于它的外层函数,外层函数的this指向谁,它的this也指向谁。
var controller = {
foo : function(){
btn.addEventListener("click", ()=>{
this.foo(); //use like controller.foo() this => controller
});
}
}
//important
var controller = {
foo : () => { //.........change.........
btn.addEventListener("click", ()=>{
this.foo(); //Error 由于词法作用域,this => 全局作用域,没有定义foo
});
}
}
7.欠正则表达式、Unicode,Symbol作为一新的原生类型,花里胡哨,除了在声明事件时,用符号来代替字符串常量,目的竟然是为了避免有意无意出现重复值。。。Symbol在迭代器以及内置对象中应用广泛,本身自定义来使用,两字谢谢。
待续。。。