算法
验证回文字符串
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。就是倒过来也一样
var isPalindrome = function(s) {
var _s = s.toLocaleLowerCase().replace(/[^a-z0-9]/g, ''),
low = 0,
high = _s.length - 1;
while(low < high){
if(_s[low] !== _s[high]){
return false;
}
++low;
--high;
}
return true
};
字符串转整数 (atoi)
实现 atoi,将字符串转为整数。
var myAtoi = function(str) {
var num = parseInt(str);
var INT_MAX = 2147483647;
var INT_MIN = -2147483648;
if (isNaN(num)) {
return 0;
}
if (num > INT_MAX) {
return INT_MAX;
}
if (num < INT_MIN) {
return INT_MIN;
}
return num;
};
实现 strStr()
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
var strStr = function(haystack, needle) {
if(needle.length === 0 || haystack.length <= needle.length){
if(haystack === needle || needle.length === 0){
return 0
}else{
return -1
}
}
for(var i=0; i<haystack.length; i++){
if(haystack[i] === needle[0]){
var _idx = i;
for(var j=0; j<needle.length; j++){
if(haystack[i+j] !== needle[j]){
_idx = -1;
break;
}
}
if(_idx !== -1){
return _idx
}
}
}
return -1
};
ES6
let 和 const
在前端终于有了块级作用域。不能在同一个块级作用域中重复声明,babel 的实现是,保证在当前作用域中,不会有其他作用域的命名与当前 let 命名重复:
// 编译钱
let num = 1;
// 编译后的结果
var num1 = 1;
const 指的是存储地址不能发生改变,如果这个值是个引用类型,是允许引用地址里的值发生改变,但是不能指向新的引用地址。babel 在编译阶段如果发现重新复制到其他的地址就会报错。
解构
数组的解构赋值
下面是一些类型的结构,结构还可以设置默认值,但是只有当值等于 undefined 时才会对结构进行赋默认值。
let [x, y, ...z,a = 0] = ['a'];
// x = 'a'
// y = undefined
// z = []
// a = 0
// f 不会执行,只有当对应的值是 undefined 时才会执行
let [x = 1, y = 2] = [undefined, null];
// x = 1
// y = null
对于 Set 结构,也可以使用数组的结构赋值
let [x, y, z] = new Set(['a', 'b', 'c']);
// x = "a"
只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。
function* fibs() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
let [first, second, third, fourth, fifth, sixth] = fibs();
sixth // 5
对象的解构赋值
对象的解构赋值和数组类似
const node = {
loc: {
start: {
line: 1,
column: 5
}
}
};
let { loc, loc: { start }, loc: { start: { line }} } = node;
// line = 1
// loc = Object {start: Object}
// start = Object {line: 1, column: 5}
let obj = {};
let arr = [];
({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });
// obj = {prop:123}
// arr = [true]
字符串结构
const [a, b, c, d, e] = 'hello';
// a = h, b = e ... #### 数值和布尔值的解构赋值
let {toString: s} = 123;
s === Number.prototype.toString // true
函数的解构
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
解构出错
// 全部报错
let [(a)] = [1];
let {x: (c)} = {};
let ({x: c}) = {};
let {(x: c)} = {};
let {(x): c
解构用途
// 交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
// 返回多个函数值赋值
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 函数参数的定义及附默认值
function f([x = 0, y, z]) { ... }
f([1, 2, 3]);
// 遍历 map
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
字符串扩展
字符串 for…of
for (let codePoint of 'foo') {
console.log(codePoint)
}
字符串新 API
// 字符串重复
let s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
// 字符串重复
'hello'.repeat(2) // "hellohello"
// 字符串补全
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
// 模板字符串
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
// Hello Bob, how are you today?
// 模板字符串嵌套
const tmpl = addrs => `
<table>
${addrs.map(addr => `
<tr><td>${addr.first}</td></tr>
<tr><td>${addr.last}</td></tr>
`).join('')}
</table>
`;
const data = [
{ first: '<Jane>', last: 'Bond' },
{ first: 'Lars', last: '<Croft>' },
];
console.log(tmpl(data));
<table>
<tr><td><Jane></td></tr>
<tr><td>Bond</td></tr>
<tr><td>Lars</td></tr>
<tr><td><Croft></td></tr>
</table>
// 模板标签
let a = 5;
let b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);
数值的扩展
// 判断整数
Number.isInteger(14) // true
Number.isInteger(3.0000000000000002) // true
// 返回整数部分
Math.trunc(4.1) // 4
Math.trunc(4.9) // 4
// 返回整数负数还是0
Math.sign()
Math.sign(-5) // -1
Math.sign(5) // +1
Math.sign(0) // +0
函数的扩展
// 默认值,使用参数默认值时,函数不能有同名参数
function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
console.log(method);
}
fetch('http://example.com')
默认值的作用域
let foo = 'outer';
function bar(func = () => foo) {
let foo = 'inner';
console.log(func());
}
bar(); // outer
rest 参数,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
// 报错
function f(a, ...b, c) {}
常见的箭头符号书写,箭头函数不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
var sum =
(num1, num2) => { return num1 + num2; }
// 报错
let getTempItem = id => { id: id, name: "Temp" };
// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });
let foo = () => { a: 1 };
foo() // undefined
箭头函数绑定 this
foo::bar;
// 等同于
bar.bind(foo);
foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);
尾调用优化,我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。
尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。
function f() {
let m = 1;
let n = 2;
return g(m + n);
}
f();
// 等同于
function f() {
return g(3);
}
f();
// 等同于
g(3);
上面代码中,如果函数g不是尾调用,函数f就需要保存内部变量m和n的值、g的调用位置等信息。但由于调用g之后,函数f就结束了,所以执行到最后一步,完全可以删除f(x)的调用帧,只保留g(3)的调用帧。
尾递归,递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。
这是因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。
- func.arguments:返回调用时函数的参数。
- func.caller:返回调用当前函数的那个函数。
数组的扩展
// ...运算符,并且有浅拷贝的作用
console.log(1, ...[2, 3, 4], 5)
Array.from
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
let arr2 = Array.from(arrayLike);
Array.of方法用于将一组值,转换为数组。
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
find() 和 findIndex()
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}) // 2
fill
['a', 'b', 'c'].fill(7)
// [7, 7, 7]
数组实例的 entries(),keys() 和 values()
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
includes()
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
[1, 2, NaN].includes(NaN) // true
对象的扩展
Object.is() 相等
Object.is('foo', 'foo') // true
Object.is({}, {}) // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
Object.assign() 合并
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
对象的枚举
(1)for…in
for…in 循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
(2)Object.keys(obj)
Object.keys 返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
const obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]
Object.entries 方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols 返回一个数组,包含对象自身的所有 Symbol 属性的键名。
(5)Reflect.ownKeys(obj)
Reflect.ownKeys 返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptor 方法会返回某个对象属性的描述对象,主要是为了解决 Object.assign() 无法正确拷贝 get 属性和 set 属性的问题。
super 关键字, 下面例子中 super.foo 等同于 Object.getPrototypeOf(this).foo(属性)或 Object.getPrototypeOf(this).foo.call(this)(方法)。
const proto = {
foo: 'hello'
};
const obj = {
foo: 'world',
find() {
return super.foo;
}
};
Object.setPrototypeOf(obj, proto);
obj.find() // "hello"