x==y 运算符
1.如果x不是正常值(比如抛出一个错误),中断执行。
2.如果y不是正常值,中断执行。
3.如果Type(x)与Type(y)相同,执行严格相等运算x === y。
4.如果x是null,y是undefined,返回true。
5.如果x是undefined,y是null,返回true。
6.如果Type(x)是数值,Type(y)是字符串,返回x == ToNumber(y)的结果。
7.如果Type(x)是字符串,Type(y)是数值,返回ToNumber(x) == y的结果。
8.如果Type(x)是布尔值,返回ToNumber(x) == y的结果。
9.如果Type(y)是布尔值,返回x == ToNumber(y)的结果。
10.如果Type(x)是字符串或数值或Symbol值,Type(y)是对象,返回x == ToPrimitive(y)的结果。
11.如果Type(x)是对象,Type(y)是字符串或数值或Symbol值,返回ToPrimitive(x) == y的结果。
12.返回false。
Es6的7种数据类型:undefined, null, Boolean, String, Number, Object, Symbol
ES6申明变量的6种方法:var, let, const, function, import, class
Set, 成员值不重复
1 | Set.prototype.constructor:构造函数,默认就是Set函数。 |
Map, 键值对的集合
1 | Map.prototype.constructor:构造函数。 |
顶层对象
- 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。
- 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self。
- Node 里面,顶层对象是global,但其他环境都不支持。
同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。
- 全局环境中,this会返回顶层对象。但是,Node 模块和 ES6 模块中,this返回的是当前模块。
- 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
- 不管是严格模式,还是普通模式,new Function(‘return this’)(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval、new Function这些方法都可能无法使用。
String
ES5 method | description |
---|---|
String.fromCharCode() | 从 Unicode 码点返回对应字符,但是这个方法不能识别码点大于0xFFFF的字符 |
String.prototype.charCodeAt() | 从字符返回对应的码点 |
String.prototype.charAt() | 返回字符串给定位置的字符 |
ES6新增了几个专门处理4字节码点的函数
ES6 method | description |
---|---|
String.fromCodePoint() | 从Unicode码点返回对应字符,可以识别码点大于0xFFFF的字符 |
String.prototype.codePointAt() | 从字符返回对应的码点 |
String.prototype.at() | 返回字符串给定位置的字符 |
Sring.raw() | 返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法 |
let text = String.fromCodePoint(0x20BB7);
for (let i = 0; i < text.length; i++) {
console.log(text[i]);
}
// " "
// " "
// for-of 可识别大于0xFFFF的码点
for (let i of text) {
console.log(i);
}
// "𠮷"
'中' === '\u4e2d' // true
String.raw`Hi\n${2+3}!`
// 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!"
String.raw`Hi\u000A!`;
// 实际返回 "Hi\\u000A!",显示的是转义后的结果 "Hi\u000A!"
String.raw`Hi\\n`
// 返回 "Hi\\\\n"
JavaScript 规定有5个字符,不能在字符串里面直接使用,只能使用转义形式。举例来说,字符串里面不能直接包含反斜杠,一定要转义写成\\
或者\u005c
U+005C:反斜杠(reverse solidus)
U+000D:回车(carriage return)
U+2028:行分隔符(line separator)
U+2029:段分隔符(paragraph separator)
U+000A:换行符(line feed)
模板字符串
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
// 模板字符串还能嵌套
const tmpl = addrs => `
<table>
${addrs.map(addr => `
<tr><td>${addr.first}</td></tr>
<tr><td>${addr.last}</td></tr>
`).join('')}
</table>
`;
标签模板
let a = 5;
let b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);
1 | # 避免 XSS 攻击 |
RegExp
// ES5 type 1
var regex = new RegExp('xyz', 'i');
// 等价于
var regex = /xyz/i;
// ES5 type 2
var regex = new RegExp(/xyz/i);
// 等价于
var regex = /xyz/i;
// ES6
new RegExp(/abc/ig, 'i').flags
// "i"
// u修饰符,支持4字节unicode
/^\uD83D/u.test('\uD83D\uDC2A') // false
/^\uD83D/.test('\uD83D\uDC2A') // true
字符串的正则方法: match() replace() search() split()
String.prototype.match 调用 RegExp.prototype[Symbol.match]
String.prototype.replace 调用 RegExp.prototype[Symbol.replace]
String.prototype.search 调用 RegExp.prototype[Symbol.search]
String.prototype.split 调用 RegExp.prototype[Symbol.split]
Symbol
每一个Symbol值都是不相等的,意味着Symbol值可以作为标识符,用于对象的属性名,就能保证不会出现同名属性。
let s = Symbol('name');
s // Symbol(name)
s.toString(); // "Symbol(name)"
let s = Symbol();
let a = {};
a[s] = 'hello';
let b = {
[s]: 'hello'
};
let c = {};
Object.defineProperty(c, s, {value: 'hello'});
a[s] // 'hello'
b[s] // 'hello'
c[s] // 'hello'
let s = Symbol();
let obj = {
[s]: function(arg) {}
};
obj[s](123);
// 增强对象的写法
let obj = {
[s](arg) {}
};
Symbol值作为属性名时,该属性是公开属性,不是私有属性
遍历
const objectSymbols = Object.getOwnPropertySymbols(obj);
Reflect.ownKeys(obj);
Array
Array 在 Javascript 中是一个对象, Array 的索引是属性名。首先, Javascript 中的 Array 在内存上并不连续,其次, Array 的索引并不是指偏移量。实际上, Array 的索引也不是 Number 类型,而是 String 类型的。我们可以正确使用如 arr[0] 的写法的原因是语言可以自动将 Number 类型的 0 转换成 String 类型的 “0″ 。所以,在 Javascript 中从来就没有 Array 的索引,而只有类似 “0″ 、 “1″ 等等的属性。有趣的是,每个 Array 对象都有一个 length 的属性,导致其表现地更像其他语言的数组。但为什么在遍历 Array 对象的时候没有输出 length 这一条属性呢?那是因为 for-in 只能遍历“可枚举的属性”, length 属于不可枚举属性,实际上, Array 对象还有许多其他不可枚举的属性。
const arr = [1, 2, 3];
for(let i = 0; i< arr.length; i++) {
console.log(arr[i]);
}
for-in 循环遍历的是对象的属性,而不是数组的索引。因此, for-in 遍历的对象便不局限于数组,还可以遍历对象。
需要注意的是, for-in 遍历属性的顺序并不确定,即输出的结果顺序与属性在对象中的顺序无关,也与属性的字母顺序无关,与其他任何顺序也无关。
const arr = [1, 2, 3];
let index;
for(index in arr) {
console.log("arr[" + index + "] = " + arr[index]);
}
forEach 不能 break 和 return;
for-in 缺点更加明显,它不仅遍历数组中的元素,还会遍历自定义的属性,甚至原型链上的属性都被访问到。而且,遍历数组元素的顺序可能是随机的。
const arr = ['a', 'b', 'c'];
for(let data of arr) {
console.log(data);
}
任何部署了Iterator接口的对象,都可以用for-of循环遍历
for-of 跟 forEach 相比,可以正确响应 break, continue, return。
for-of 循环不仅支持数组,还支持大多数类数组对象,例如 DOM nodelist 对象。
for-of 循环也支持字符串遍历,它将字符串视为一系列 Unicode 字符来进行遍历。
for-of 也支持 Map 和 Set (两者均为 ES6 中新增的类型)对象遍历。
但需要注意的是,for-of循环不支持普通对象,但如果你想迭代一个对象的属性,你可以用 for-in 循环(这也是它的本职工作)。
最后要说的是,ES6 引进的另一个方式也能实现遍历数组的值,那就是 Iterator。上个例子:
const arr = ['a', 'b', 'c'];
const iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
every: 循环在第一次 return false 后返回
some: 循环在第一次 return true 后返回
filter: 返回一个新的数组,该数组内的元素满足回调函数
map: 将原数组中的元素处理后再返回
reduce: 对数组中的元素依次处理,将上次处理结果作为下次处理的输入,最后得到最终结果。
尾调用
尾调用(Tail Call)是函数式编程的一个重要概念,就是指某个函数的最后一步是调用另一个函数。
尾调用优化,只保留内层函数的调用帧
尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
尾递归,由于只存在一个调用帧,不会发生栈溢出错误
尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。
递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。对于其他支持“尾调用优化”的语言(比如 Lua,ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。
es6中只有严格模式'use strict'
才能开启尾递归优化
// 蹦床函数,将递归执行转为循环执行
function trampoline(f) {
while (f && f instanceof Function) {
f = f();
}
return f;
}
// 真正的尾递归优化
function tco(f) {
let value;
let active = false;
const accumulated = [];
return function accumulator(...args) {
accumulated.push(args); // 第二次及以后还是会push参数
if (!active) {
// 第二次及以后返回undefined ,从而避免递归执行
active = true;
while (accumulated.length) {
value = f.apply(this, accumulated.shift()); // f运行 =》 push参数 =》 while条件为真继续执行
}
active = false;
return value;
}
};
}
var sum = tco(function(x, y) {
if (y > 0) {
return sum(x + 1, y - 1)
}
else {
return x
}
});
sum(1, 100000)
// 100001
可枚举性
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }
目前,有四个操作会忽略enumerable为false的属性。
for…in循环:只遍历对象自身的和继承的可枚举的属性。
Object.keys():返回对象自身的所有可枚举的属性的键名。
JSON.stringify():只串行化对象自身的可枚举的属性。
Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
ES6 一共有 5 种方法可以遍历对象的属性。
(1)for…in
for…in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
(2)Object.keys(obj)
Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名
(5)Reflect.ownKeys(obj)
Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举
以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
首先遍历所有数值键,按照数值升序排列。
其次遍历所有字符串键,按照加入时间升序排列。
最后遍历所有 Symbol 键,按照加入时间升序排列。
严格模式主要有以下限制
变量必须声明后再使用
函数的参数不能有同名属性,否则报错
不能使用with语句
不能对只读属性赋值,否则报错
不能使用前缀 0 表示八进制数,否则报错
不能删除不可删除的属性,否则报错
不能删除变量delete prop,会报错,只能删除属性delete global[prop]
eval不会在它的外层作用域引入变量
eval和arguments不能被重新赋值
arguments不会自动反映函数参数的变化
不能使用arguments.callee
不能使用arguments.caller
禁止this指向全局对象
不能使用fn.caller和fn.arguments获取函数调用的堆栈
增加了保留字(比如protected、static和interface)