第一章 块级作用域绑定
var
声明初始化变量, 声明可以提升,但初始化不可以提升。
一、块级声明:
let
const
- 默认使用,在某种程度上实现代码不可变,减少错误发生的几率
- 如果常量是对象,则对象中的值可以修改
- 不能重复声明,声明不会提升
二、临时死区:
JS引擎在扫描代码发现变量声明时,要么将它们提升至作用域顶部(
var
声明),要么将声明放到TDZ中(let
和const
声明)。访问TDZ中的变量会触发运行时错误。只有执行过变量声明语句后,变量才会从TDZ中移出,然后可以正常访问。
第一种情况:
if(condition) {
console.log(typeof value); //引用错误!
let value = "blue"; //TDZ
}
复制代码
第二种情况:
console.log(typeof value); //'undefined'-->只有变量在TDZ中才会报错
if(condition) {
let value = "blue";
}
复制代码
三、循坏中块作用域绑定
-
立即调用(IIFE)
-
let
和const
之所以可以在运用在for-in和for-of循环中,是因为每次迭代会创建一个新的绑定(const在for循环中会报错)。
四、全局块作用域绑定
var
可能会在无意中覆盖一个已有的全局属性let
或const
,会在全局作用域下创建一个新的绑定,但该绑定不会添加为全局对象的属性。换句话说,使用let
或const
不能覆盖全局变量,而只能遮蔽它。如果不行为全局对象创建属性,使用let
和const
要安全得多。
注:如果希望子安全局对象下定义变量,仍然可以使用var。这种情况常见于在浏览器中跨frame或跨window访问代码。
第二章 字符串和正则表达式
一、UTF-8码位
名词解释:
- 码位: 每一个字符的“全球唯一的标识符,从0开始的数值”
- 字符编码:表示某个字符的数值或码位即为该字符的字符编码。
- 基本多文种平面(BMP,Basic Multilingual Plane)
在UTF-16中,前2^16个码位均以16位的编码单元表示,这个范围被称作基本多文种平面
二、codePointAt()、 String.fromCodePoint() 和 normalize()
- 两个方法对应于charCodeAt()和fromCharCode()
- normalize(): 规范的统一,适用于比较排序,国际化。
三、正则表达式 u 和 y 修饰符、正则表达式的复制、flag属性
- u: 编码单元 ---> 字符模式
这个方法尽管有效,但是当统计长字符串中的码位数量时,运动效率很低。因此,你也可以使用字符串迭代器解决效率低的问题,总体而言,只要有可能就尝试着减小码位计算的开销。
检测u修饰符支持:
function hasRegExpU() {
try {
var pattern = new RegExp('.', 'u');
return true;
} catch (ex) {
return false;
}
}
复制代码
- y: 第一次匹配不到就终止匹配
当执行操作时, y修饰符会把上次匹配后面一个字符的索引保存到
lastIndexOf
中;如果该操作匹配的结果为空,则lastIndexOf
会被重置为0。g修饰符的行为类似。
1. 只有调用exec()和test()这些正则表达式对象的方法时才会涉及lastIndex属性;
2. 调用字符串的方法,例如match(),则不会触发粘滞行为。
复制代码
- 正则表达式的复制
var re1 = /ab/i;
re2 = new RegExp(re1); //没有修饰符复制
re3 = new RegExp(re1, "g"); //有修饰符(ES6)
复制代码
- flag属性 --- 获取正则表达式的修饰符
es5方法获取正则表达式的修饰符:
function getFlags(re) {
var text = re.toString();
return text.substring(text.lastIndexOf('/' + 1, text.length);
}
复制代码
模板字面量
多行字符串
基本的字符串格式化(字符串占位符)
HTML转义
- 标签模板
function passthru(literals, ...substitutions) {
//返回一个字符串
let result = "";
//根据substitutions的数量来确定循环的执行次数
for(let i=0; i<substitutions.length; i++){
result += literals;
result += substitutions[i]
}
//合并最后一个literal
result += literals[literals.length - 1];
return result;
}
let count = 10;
price = 0.25;
message = passthru`${count} items cost $${(count * price).toFixed(2)}`;
console.log(message)
复制代码
- String.raw
String.raw`assda\\naadasd`
//代码模拟(略)
复制代码
第三章 函数
一、默认参数值
ES5默认参数值
下面函数存在什么问题 ???
function makeRequest(url, timeout, callback) {
timeout = timeout || 2000;
callback = callback || function() {};
}
复制代码
假如timeout
传入值0,这个值是合法的,但是也会被视为一个假值,并最终将timeout
赋值为2000。在这种情况下,更安全的选择是通过typeof
检查参数类型,如下:
function makeRequest(url, timeout, callback) {
timeout = (typeof timeout !== 'undefined') ? timeout :2000;
callback = (typeof callback !== 'undefined') ? callback : function() {};
}
复制代码
ES5默认参数值
function makeRequest(url, timeout = 2000, callback) {
//函数的其余部分
}
//特别注意:此时 null 是一个合法值,所以不会使用 timeout 默认值,即 timeout = null
makeRequest('/foo', null, function(body){
doSomething(body);
})
复制代码
二、默认参数值对arguments
的影响**
- ES5:
非严格模式:参数变化,arguments对象随之改变;
严格模式:无论参数如何变化,arguments对象不再随之改变;
- ES6
非严格模式/严格模式:无论参数如何变化,arguments对象不再随之改变;
注: 在引用参数默认值的时候,只允许引用前面参数的值,即先定义的参数不能访问后定义的参数。这可以用默认参数的临时死区来解释。如下:
function add(first = second, second) {
return first + second;
}
console.log(add(1, 1)); //2
console.log(add(undefined, 1)) //抛出错误
//解释原理:
//add(1, 1)
let first = 1;
let second =1;
//add(undefined, 1)
let first = second;
let second = 1; //处于临时死区
复制代码
三、不定参数的使用限制
- 每个函数最多只能声明一个不定参数,而且移动要放在所有参数的末尾。
- 不定参数不能用于对象字面量
setter
之中(因为对象字面量setter的参数有且只有一个,而在不定参数的定义中,参数的数量可以无限多)
无论是否使用不定参数,arguments对象总是包含所有传入函数的参数。
四、展开运算符
let value = [25, 50, 75, 100];
//es5
console.log(Math.max.apply(Math, values); //100
//es6
console.log(Math.max(...values)); //100
复制代码
五、name 属性
两个有关函数名称的特例:
- 通过
bind(1)
函数创建的函数,其名称将带有“bound”前缀; - 通过
Function
构造函数创建的函数,其名称将是“anonymous”.
var doSomething = function() {
//空函数
}
console.log(doSomething.bind().name); //'bound doSomething'
console.log((new Function()).name); //'anonymous'
复制代码
切记: 函数name属性的值不一定引用同名变量,它只是协助调试用的额外信息,所以不能使用name属性的值来获取对于函数的引用。
六、明确函数的多重用途
JS函数有两个不同的内部方法:[[call]] 和 [[Construct]]。
- 当通过new关键字调用函数是,执行的是 [[Construct]] 函数,它负责创建一个通常被称为实例的新对象,然后再执行函数体,将
this
绑定到实例上(具有 [[Construct]] 方法的函数被统称为构造函数,箭头函数没有 [[Construct]] 方法 ); - 如果不通过
new
关键字调用函数,则执行 [[call]] 函数,从而直接执行代码中的函数体;
七、元属性(Metaproperty)new.target
为了解决判断函数是否通过new关键字调用的问题,new.target
横空出世 (instance of ---> new.target)
在函数外使用
new.target
是一个语法错误。
八、块级函数
- ES5严格模式下,代码块中声明函数会报错;
- ES6严格模式下, 可以在定义该函数的代码块中访问和调用它 (块级函数提升,let变量不提升);
- ES6非严格模式下,函数不再提升至代码块的顶部,而是提升至外围函数或全局作用域的顶部。
九、箭头函数
箭头函数与传统的JS函数不同之处主要有一下一个方面:
- 没有
this
、super
、arguments
和new.target
绑定; - 不能通过
new
关键字调用; - 没有原型;
- 不可以改变
this
的绑定; - 不支持
arguments
对象 - 不支持重复的命名参数
创建一个空函数:
let doNothing = () => {};
复制代码
返回一个对象字面量
let getTempItem = id => ({ id: id, name: "Temp"});
复制代码
创建立即执行的函数
let person = ((name) => {
return {
getName: function() {
return name;
}
}
})("xszi")
console.log(person.getName()); //xszi
复制代码
箭头函数没有this
绑定
let PageHandler = {
id: '123456',
init: function() {
document.addEventListener("click", function(event){
this.doSomething(event.type); //抛出错误
}, false)
},
doSomething: function(type) {
console.log("handling " + type + "for" + this.id)
}
}
复制代码
使用bind()
方法将函数的this
绑定到PageHandler
,修正报错:
let PageHandler = {
id: '123456',
init: function() {
document.addEventListener("click", (function(event){
this.doSomething(event.type); //抛出错误
}).bind(this), false)
},
doSomething: function(type) {
console.log("handling " + type + "for" + this.id)
}
}
复制代码
使用箭头函数修正:
let PageHandler = {
id: '123456',
init: function() {
document.addEventListener("click",
event => this.doSomething(event.type), false);
},
doSomething: function(type) {
console.log("handling " + type + "for" + this.id)
}
}
复制代码
- 箭头函数没有
prototype
属性,它的设计初衷是 即用即弃, 不能用来定义新的类型。- 箭头函数的中
this
取决于该函数外部非箭头函数的this
值,不能通过call(), apply()
或bind()
方法来改变this
的值。
箭头函数没有arguments
绑定
始终访问外围函数的
arguments
对象
十、尾调用优化
- ES5中,循环调用情况下,每一个未完成的栈帧都会保存在内存中,当调用栈变的过大时会造成程序问题。
- ES6中尾调用优化,需要满足一下三个条件:
- 尾调用不访问当前栈帧的变量(也就是说函数不是一个闭包);
- 在函数内部,尾调用是最后一条语句;
- 尾调用的结果作为函数值返回;
如何利用尾调用优化
function factorial(n) {
if ( n<=1 ) {
return 1;
}
}else{
//引擎无法自动优化,必须在返回后执行乘法操作
return n * factorial(n-1);
//随调用栈尺寸的增大,存在栈溢出的风险
}
复制代码
function factorial(n, p = 1) {
if ( n<=1 ) {
return 1 * p;
}
}else{
let result = n * p;
//引擎可自动优化
return factorial(n-1, result);
//不创建新的栈帧,而是消除并重用当前栈帧
}
复制代码