naseeihity / LearnToLearn

MIT License
28 stars 8 forks source link

学习ES6系列——基础 #24

Open naseeihity opened 7 years ago

naseeihity commented 7 years ago

基础快速梳理

新的基本语法

  1. 块级作用域
// ES6新增了块级作用域
// let,const声明的变量
// 函数可以声明在块级作用域内,但由于浏览器兼容性的问题,浏览器的实现可能会不同,应避免使用,可以用下面的方法替代

// 函数声明语句
{
  let a = 'secret';
  function f() {
    return a;
  }
}

// 函数表达式替代
{
  let a = 'secret';
  let f = function () {
    return a;
  };
}
  1. 新的赋值方法
// 解构赋值
// 数组
let [a, b, c] = [1, 2, 3];
let [head, ...tail] = [1, 2, 3, 4];
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

// 对象
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
// => baz = {foo:'aaa', bar:'bbb'}.foo
baz // "aaa"

// 字符串,会被转化成一个类数组对象
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
let {length : len} = 'hello';
len // 5
// 1.变量交换
[x, y] = [y, x];

// 2. 从函数返回多个值,输入模块的指定方法
const { SourceMapConsumer, SourceNode } = require("source-map");

// 3. 提取JSON数据
let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data: number } = jsonData;

// 4. 函数参数的默认值
jQuery.ajax = function (url, {
  async = true,
  beforeSend = function () {},
  cache = true,
  complete = function () {},
  crossDomain = false,
  global = true,
  // ... more config
}) {
  // ... do stuff
};

// 5. 便利 map
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}
// first is hello
// second is world

// 获取键名
for (let [key] of map) {
  // ...
}

// 获取键值
for (let [,value] of map) {
  // ...
}
  1. 基本扩展
// Unicode编码相关,用到的时候再看
String.fromCharCode,codePointAt,normalize()

// for..of 遍历字符串
let text = String.fromCodePoint(0x20BB7);

for (let i = 0; i < text.length; i++) {
  console.log(text[i]);
}
// " "
// " "

for (let i of text) {
  console.log(i);
}
// "𠮷"

// 判断一个字符串是否包含在另一个字符串中
let s = 'Hello world!';

s.startsWith('world', 6) // true,从第 n 个位置到字符串结束
s.endsWith('Hello', 5) // true,针对前 n 个字符
s.includes('Hello', 6) // false

// repeat,将原字符串重复几次,返回一个新的字符串
'na'.repeat(2.9) // "nana"

带来了新的 XSS Payload

// 使用`来定义,可以传入变量${},{}中甚至可以调用函数,所有的空格缩进都会被保留。
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

// 标签模板——防止 xss,国际化处理
let message =
  SaferHTML`<p>${sender} has sent you a message.</p>`;

function SaferHTML(templateData) {
  let s = templateData[0];
  for (let i = 1; i < arguments.length; i++) {
    let arg = String(arguments[i]);

    // Escape special characters in the substitution.
    s += arg.replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;");

    // Don't escape special characters in the template.
    s += templateData[i];
  }
  return s;
}

let sender = '<script>alert("abc")</script>'; // 恶意代码
let message = SaferHTML`<p>${sender} has sent you a message.</p>`;

message
// <p>&lt;script&gt;alert("abc")&lt;/script&gt; has sent you a message.</p>

i18n`Welcome to ${siteName}, you are visitor number ${visitorNumber}!`
// "欢迎访问xxx,您是第xxxx位访问者!"

// String.raw()内置的模板字符串标签函数,返回一个斜杠被转义(如果已经转义,则不会做任何处理),变量被替换后的模板字符串
String.raw`Hi\n${2+3}!`;
// "Hi\\n5!"

String.raw`Hi\u000A!`;
// 'Hi\\u000A!'
var regex = new RegExp(/xyz/, 'i');
// 四个可以使用正则表达式的方法:match()、replace()、search()和split()
// 扩展运算符,将数组转为逗号分隔的参数序列。
---
// 1. 替代 apply 方法将数组转为参数序列,可供 Math.max等方法使用
// 2. 将数组 push 进一个数组中
console.log(...[1, 2, 3])
// 1 2 3

// 数组深度复制
const a1 = [1, 2];
// 写法一
const a2 = [...a1];

// 合并数组
[...arr1, ...arr2, ...arr3]

// 生成数组
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]

// 字符串转数组
[...'hello']
// [ "h", "e", "l", "l", "o" ]

// 将实现了迭代器(Iterator)接口的对象转为数组
let nodeList = document.querySelectorAll('div');
let array = [...nodeList];

let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

let arr = [...map.keys()]; // [1, 2, 3]

const go = function*(){
  yield 1;
  yield 2;
  yield 3;
};

[...go()] // [1, 2, 3]

---

// Array.from()将类数组对象(所谓类似数组的对象,本质特征只有一点,即必须有length属性。)和可遍历对象转换为真正的数组
let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

// 还可以有第二个参数,如果map函数里面用到了this关键字,还可以传入Array.from的第三个参数,用来绑定this。
Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);

---

// Array.of(),用来替代Array()或new Array(),并且不存在由于参数不同而导致的重载。

Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
// 对比
Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]

//实现
function ArrayOf(){
  return [].slice.call(arguments);
}

---

// copyWithin(),将制定位置的成员复制到其他位置,返回修改后的数组
Array.prototype.copyWithin(target, start = 0, end = this.length)
[1, 2, 3, 4, 5].copyWithin(0, 3) // [4, 5, 3, 4, 5]

---

// find(), findIndex()找到第一个符合条件的数组成员并返回该值或该值的位置
[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10

---

// fill()填充数组,数组中已有的元素会被抹去
[].fill(target, start, end)

---

// entries(),keys()和values()返回遍历器对象,配合 for..of 循环进行遍历,或.next()方法

// 遍历键名
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()表示某个数组是否包含给定的值,支持 NaN
[1, 2, NaN].includes(NaN) // true

---

// 空位规则处理不统一,应该避免出现
// 将数组的空位有的转换为 undefined
Array.from(['a',,'b'])
// [ "a", undefined, "b" ]
[...['a',,'b']]
// [ "a", undefined, "b" ]

// 有的没有
[,'a','b',,].copyWithin(2,0) // [,"a",,"a"]
new Array(3).fill('a') // ["a","a","a"]
// for..of 会跳过空位,map 会遍历空位
// 支持对象中直接写入变量和函数,属性名为变量名,属性值为变量值
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}

// 等同于
const baz = {foo: foo};

// 于是有了更简洁的函数的写法
const o = {
  method() {
    return "Hello!";
  }
};

// 等同于

const o = {
  'method': function() {
    return "Hello!";
  }
};

// 输出模块更简洁
function clear () {
  ms = {};
}

module.exports = { clear };

---

// 允许使用对象字面量定义对象时使用表达式作为属性名
let propKey = 'foo';

let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};

// 注意:属性名表达是为对象时会被转化
const keyA = {a: 1};
const keyB = {b: 2};

const myObject = {
  [keyA]: 'valueA',
  [keyB]: 'valueB'
};

myObject // Object {[object Object]: "valueB"}

---

// 对象方法和函数一样具有 name 属性,获得函数(方法)名
const person = {
  sayName() {
    console.log('hello!');
  },
};

person.sayName.name   // "sayName"

---

// Object.is()比较两个值是否相等,取代严格相等比较符,具有更加同一的行为
+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

// 实现方法
Object.defineProperty(Object, 'is', {
  value: function(x, y) {
    if (x === y) {
      // 针对+0 不等于 -0的情况
      return x !== 0 || 1 / x === 1 / y;
    }
    // 针对NaN的情况
    return x !== x && y !== y;
  },
  configurable: true,
  enumerable: false,
  writable: true
});

---

// Object.assign()用于对象合并,后面的同名属性会覆盖前面的,不拷贝继承属性,也不拷贝不可枚举属性
const target = { a: 1, b: 1 };

const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

// 注意:该方法实现的是浅拷贝,属性值为对象时,目标得到的是对象的引用
const obj1 = {a: {b: 1}};
const obj2 = Object.assign({}, obj1);

obj1.a.b = 2;
obj2.a.b // 2

// 处理数组
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]

// 用途
// 1. 为对象添加属性
class Point {
  constructor(x, y) {
    Object.assign(this, {x, y});
  }
}

// 2. 为对象添加方法
Object.assign(SomeClass.prototype, {
  someMethod(arg1, arg2) {
    ···
  },
  anotherMethod() {
    ···
  }
});

// 3. 浅度克隆对象
// 深度克隆就方便的方法:JSON.parse(JSON.stringify(obj)),只是对 function 和 Date 处理有问题
let copy = Object.assign({}, obj);

// 保持继承链
function clone(origin) {
  let originProto = Object.getPrototypeOf(origin);
  return Object.assign(Object.create(originProto), origin);
}

// 4. 合并多个对象
const merge =
  (...sources) => Object.assign({}, ...sources);

// 5. 制定参数属性默认值
const DEFAULTS = {
  logLevel: 0,
  outputFormat: 'html'
};

function processContent(options) {
  options = Object.assign({}, DEFAULTS, options);
  console.log(options);
  // ...
}

---

//for...in循环:只遍历对象自身的和继承的可枚举的属性。大多数情况应考虑使用 Object.keys()代替

---

// __proto__属性,用来读取和设置对象的 prototype 属性,只部署在浏览器环境,应使用Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。

---

// super 关键字:指向当前对象的原型对象,表示原型对象时只能用于方法中,且只能用于对象方法的简写法
// 报错
const obj = {
  foo: super.foo
}

// 报错
const obj = {
  foo: () => super.foo
}

// 报错
const obj = {
  foo: function () {
    return super.foo
  }
}

// OK
const obj = {
  foo() {
    return super.foo;
  }
};
// 为函数的参数设置默认值,通常设置默认值得参数应在参数列表的尾部,以实现可以省略他的目的
function log(x, y = 'World') {
  console.log(x, y);
}

function foo(x = 5) {
  // 不能在函数体中再次声明 x
  // 一个好的代码风格:不论是否函数的参数设置过默认值,在函数体内都不要声明和参数同名的变量
  let x = 1; // error
  const x = 2; // error
}

// 与解构值配合使用
function foo({x, y = 5} = {}) {
  console.log(x, y);
}
// 没有提供参数时会默认传入一个空对象
foo() // undefined 5

---

// 函数的 length 属性:表示该函数预期传入的参数个数

---

// 函数参数的初始化会形成单独的作用域
var x = 1;

function f(x, y = x) {
  console.log(y);
}

f(2) // 2

let x = 1;

// 单独形成作用域,作用域中没有找到 x 的值,所以找到原型链上的 x 而不是函数内部的 x
function f(y = x) {
  let x = 2;
  console.log(y);
}

f() // 1

---

// 利用函数参数的默认值可以显示地制定某一个参数不可省略,省略就抛出错误
function throwIfMissing() {
  throw new Error('Missing parameter');
}

function foo(mustBeProvided = throwIfMissing()) {
  return mustBeProvided;
}

foo()
// Error: Missing parameter

// 声明为 undefined 表明参数可以省略
function foo(optional = undefined) { ··· }

---

// rest 参数,取代 arguments,所对应的变量是个数组
function push(array, ...items) {
  items.forEach(function(item) {
    array.push(item);
    console.log(item);
  });
}

var a = [];
push(a, 1, 2, 3)

---
// 基本用法
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

// 箭头函数的函数体如果多于一条语句,应用{}括起来,且使用 return 返回
var sum = (num1, num2) => { num1++; return num1 + num2; }

// 箭头函数返回对象时需要在对象外面加上括号
let getTempItem = id => ({ id: id, name: "Temp" });

// 和变量解耦配合使用
const full = ({ first, last }) => first + ' ' + last;

// 等同于
function full(person) {
  return person.first + ' ' + person.last;
}

---

// this, super, arguments, new.target在箭头函数中都不存在,他们都指向定义时外层函数中对应变量

// 因此,也无法通过 call, apply, bind 改变 this 的指向

---

// 尾调用优化:只保留内部函数的调用帧
function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// 等同于
function f() {
  return g(3);
}
f();

// 等同于
g(3);

---

// 尾递归:只保留了一个调用记录,避免的堆栈溢出。只有在严格模式生效,正常模式失效
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
  if( n <= 1 ) {return ac2};

  return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}

Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity

// 尾递归函数改写:确保函数最后一句只调用自身,需要将内部变量变成参数形式。但是这样会很不直观,可以通过下面两种方法规避

// 方法一:currying
function currying(fn, n) {
  return function (m) {
    return fn.call(this, m, n);
  };
}

function tailFactorial(n, total) {
  if (n === 1) return total;
  return tailFactorial(n - 1, n * total);
}

const factorial = currying(tailFactorial, 1);

factorial(5) // 120

// 方法二:函数默认值
function factorial(n, total = 1) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5) // 120

// 手动实现尾递归优化:减少调用栈 -> 使用‘循环’替换的‘递归’

function tco(f) {
  var value;
  var active = false;
  var accumulated = [];

  return function accumulator() {
    accumulated.push(arguments);
    if (!active) {
      active = true;
      while (accumulated.length) {
        value = f.apply(this, accumulated.shift());
      }
      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)

编码规范

// bad
const baz = [...foo].map(bar);

// good
const baz = Array.from(foo, bar); 

---

// bad
function processInput(input) {
  // then a miracle occurs
  return [left, right, top, bottom];
}

// the caller needs to think about the order of return data
const [left, __, top] = processInput(input);

// good
function processInput(input) {
  // then a miracle occurs
  return { left, right, top, bottom };
}

// the caller selects only the data they need
const { left, top } = processInput(input);