ES6 标准入门

Posted lu0511

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ES6 标准入门相关的知识,希望对你有一定的参考价值。

1、let所声明的变量只在let命令所在的代码块有效

2、不存在变量提升

在代码块内,使用let命令声明变量之前,该变量都是不可用的,称为暂时性死区。

3、不允许重复声明

let 不允许在相同作用域内重复声明一个变量

4、块级作用域

没有块级作用域的不合理的场景

ar temp = new Date();
function f()
{ 
   console.log(temp);
   if(true)// true或false结果一样
    {
      var temp = "hello"
     }
}
f() //undefined

因为变量提升,导致内层的tmp变量覆盖了外层的tem变量

提升的为变量声明 var tmp

但是没有初始化,在if判断里才对声明的变量初始化

块级作用域的出现实际上使得广泛应用的立即执行函数不再必要了

//IIFE写法function() {
   var tmp = ...;
}());
//块级作用域写法
{
   let tmp = ...;
}

5、const命令

 1)const用来声明常量。一旦声明,其值就不能改变

2)只在声明块级作用域有效

3)const命令声明的常量也不提升

4)对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。const命令只是保证变量名指向的地址不变,并不保证该地址的数据不变,所以将一个对象声明为常量必须十分小心

6、ES6一共有6种声明变量的方法:

var,function,let ,const,import,class

7、全局对象的属性

let,class,const声明的全局变量不属于全局变量的属性(仅仅不是他的属性)

8、变量的解构赋值

解构赋值允许指定默认值

 var [a,b,c] = [1,2,3]

9、对象的解构赋值

var {foo,bar} = {foo:"aaa",bar:"bbb"}

对象的解构和数组有个重要的不同,数组的元素是按次序排列的,变量由它的位置决定。

对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

第七章 数组的扩展

1、Array.from

Array.from 用于将两类对象转为真正的数组:类似数组的对象和可遍历对象

下面是一个类似数组的对象:

 

let arraylike = {
  \'0\': \'a\',
  \'1\': \'b\',
  \'2\': \'c\',
  length: 3
};

// ES5的写法
var arr1 = [].slice.call(arraylike);//[\'a\',\'b\',\'c\']
//es6的写法
let arr2 = Array.from(arraylike);//[\'a\',\'b\',\'c\']

 

实际应用中,常见的类似数组的对象是DOM操作返回的Nodelist集合,以及函数内部的arguments对象,Array.from 都可以将他们转为真正的数组

 只要是部署了interater接口的数据结构,Array.from 都能将其转为数组

set 

所谓类似数组的对象,本质特征只有一点,必须有length属性,因此任何有 length属性的对象,都可以通过Array.from()转为数组

Array.from第二个参数类似map

Array.from(arraylike,x=>x*x)
Array.from(arraylike).map(x => x*x)

 Array.of用于将一组值转为数组

copyWithin()

复制数组,会修改当前数组

find()

用于找到第一个符合条件的数组成员,他的参数为一个回调函数,所有成员执行这个回调函数,找到第一个返回true的成员,返回该成员,否则返回undefined

findIndex()

返回第一个符合条件的数组成员的位置,所有都不符合返回-1

fill()

使用给定值填充数组

entries(),keys(),values()

遍历数组

includes()

返回一个布尔值,表示某个数组是否包含给定的值

之前使用indexof,有两个缺点1、不够语义化 2、内部使用严格运算符(===)判断,对NaN误判

map和set解构有一个has注意区分,Map.prototype.has(key) 查找键名

Set.prototype.has(value)查找值

数组的空位

空位不是undefined一个位置等于undefined依然是有值的,空位是没有任何值

0 in [undefined,undefined,undefined]//true
0 in [, , ,]//false

0号位置有值,没值

Array.from()、扩展运算符会将空位转为undefined

数组推导

允许直接通过现有数组生成新数组

var a1 = [1,2,3,4];
var a2 = [for (i of a1) i*2];
a2 //[2,4,6,8]

 

 第九章 函数的扩展

1、ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法

es6允许为函数的参数设置默认值,直接写在参数定义的后面

function log(x,y = \'World\'){
  console.log(x,y)
  }

参数变量时默认声明的,所以不能用let或者const再次声明

 

2、函数的length属性

指定了默认值以后,函数的length属性将返回没有指定默认值的参数个数

(function(a){}).length // 1
(function(a = 5){}).length // 0

3、作用域

如果参数默认值是一个变量,则该变量所处的作用域与其他变量的作用域规则是一样的,先是当前函数的作用域然后才是全局作用域

let x = 1;
function f(x,y = x){
  console.log(y)
}
f(2);//2

以上,函数作用域的内部变量x已经生成,所以y等于参数x而不是全局变量x

let x = 1;
function f(y = x){
  let x = 2;
  console.log(y)
}
f();//1

函数调用时y的默认值变量x还没有在函数内部生成,所以x指向全局变量

 如果函数A的参数默认值是函数B,那么由于函数的作用域是其声明时所在的作用域,函数B的作用域就不是函数A而是全局作用域。

4、rest参数

...变量名

用于获取函数的多余参数,这样就不需要使用arguments对象了

rest参数之后不能有其他参数,函数length属性不包括rest参数

5、扩展运算符

...

好比rest参数的逆运算,将一个数组转为用逗号分割的参数序列

console.log(...[1,2,3]) //1 2 3

 6、替代数组的apply方法

由于扩展运算符可以展开数组,所以不需要apply方法将数组转为函数的参数了

//ES5 的写法
function f(x,y,z){}
var args = [0,1,2];
f.apply(null,args);

//ES6的写法
function f(x,y,z){}
var args = [0,1,2];
f(...args);

7、扩展运算符的应用

1)合并数组

var arr1 = [\'a\',\'b\']
var arr2 = [\'c\']
var arr3 = [\'d\',\'e\']

//es5
arr1.concat(arr2,arr3)
//es6
[...arr1,...arr2,...arr3]

 2)字符串

扩展运算符可以将字符串专为真正的数组

8、name属性

函数的name属性返回该函数的函数名

function foo() {}
foo.name // \'foo\'

9、箭头函数

var sum =(num1, num2) => num1 + num2;
//等同于
var sum =function(num1, num2) {
  return num1 + num2;
}

如果箭头函数的代码多于一条语句,就要使用大括号将其括起来,并使用return语句

箭头函数可以与变量解构结合使用

const full = ({first,last}) => first + \'\' + last;
//等同于
function full(person) {
  return person.first + \'\' +person.name;
}

箭头函数使用注意点:

1、函数体内this 对象就是定义时所在的对象,而不是使用时所在的对象

2、不可以当作构造函数,不可以使用new命令

3、不可以使用arguments对象,该对象在函数体内不存在,如果要用可以用rest参数代替

this指向的固定化并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this

由于箭头函数没有自己的this,当然也就不能用call(),apply(),bind()这些方法去改变this的指向

10、尾调用优化

 尾调用是函数式编程的一个重要概念

就是指某个函数的最后一步是调用另一个函数

function f(x){
  return g(x);
}

函数调用会在内存中形成一个‘调用记录’。称为调用帧,保存调用位置和内部变量的信息。

如果在函数A的内部调用函数B,那么在A的调用帧的上方还会形成B的调用帧,等到b运行结束,将结果返回A,B的调用帧才会消失

尾调用由于是函数最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置,内部变量等信息都不会再用到了,直接用内层函数的调用帧取代外层函数即可

function f() {
  let m = 1;
  let n = 2;
  return g(m+n);
}
f()
//等同于
function f() {
  return g(3);
}
f()
//等同于
g(3)

11、尾递归

 递归非常耗费内存,因为同时需要保存成百上千个调用帧,很容易发生栈溢出错误,但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生栈溢出错误

非尾调用,

function factorial(n) {
  if(n === 1) return 1;
  return n * factorial(n-1);
}
factorial(5) // 120

尾调用

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

 如果改写为一个尾递归,只保留一个调用记录,复杂度为O(1)

尾递归的实现往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法就是把所有用到的内部变量改写成函数的参数

缺点是不太直观

方法一:在尾递归函数之外再提供一个正常形势的函数

function tailfactorial(n, total) {
  if(n === 1) return total;
  return factorial(n - 1,n*total);
}
function factorial(n) {
  return tailfactorial(n,1);
}
factorial(5);

函数式编程有一个概念:柯里化 :将多参数的函数转换成单参数的形式

柯里化是这样的一个转换过程,把接受多个参数的函数变换成接受一个单一参数(译注:最初函数的第一个参数)的函数,如果其他的参数是必要的,返回接受余下的参数且返回结果的新函数。

function currying(fn,n) {
  return function(m) {
    return fn.call(this, m, n)
  }
}
function tailfactorial(n, total) {
  if(n === 1) return total;
  return factorial(n - 1,n*total);
}
const factorial = currying(tailfactorial, 1);
factorial(5)

方法二:采用ES6的默认值

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

 第9章 对象的扩展

1、ES6允许在对象中只写属性名不写属性值

function f(x,y){
  return {x,y};
}
//等同于
function f(x,y){
  return {x:x,y:y};
}

2、属性名表达式

方法一:

obj.foo = true

方法二:

obj[\'a\'+\'bc\'] = 123

3、方法的name属性

函数的name属性返回函数名,对象方法也是函数,因此也有name属性

 如果对象方法是一个symbol值,那么name属性返回的是这个symbol值的描述

每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。

4、object.is()

object.is 用来比较两个值是否严格相等,它与严格比较运算符(===)的行为基本一致

Object.is(\'foo\',\'foo\') // true
Object.is({},{}) // false

5、Object.assign() 这是一种浅复制

用来将源对象的所有可枚举属性复制到目标对象。它至少需要两个对象作为参数,第一个参数是目标对象,后面的参数都是源对象。

只要有一个参数不是对象,就会抛出错误

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

如果目标属性和源对象有同名属性,或者多个源对象有同名属性,后面的属性会覆盖前面的属性

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

 Object.assign()  只复制自身属性,不可枚举属性和继承属性不会被复制。

对于嵌套的对象,Object.assign() 处理方法是替换而不是添加

var target = {a:{b:\'c\',d:\'e\'}}
var source = {a:{b:\'hello\'}}
Object.assign(target,source);//{a:{b:\'hello\'}}

为对象添加属性

为对象添加方法

Object.assign(someClass.prototype,{
  someMethod(arg1, arg2) {
    ...
  },
  anotherMethod() {

  }
});

克隆对象

不过这种方法克隆,只能克隆原始对象自身的值,不能克隆他继承的值

function clone(origin) {
  return Object.assign({},origin);
}

合并多个对象

将多个对象合并到某个对象,如果希望合并后返回一个新对象,可以对一个空对象合并

const merge = (...sources) => Object.assign({},...sources);

为属性指定默认值

6、属性的可枚举性

for...in:只遍历对象自身的和继承的可枚举属性

object.keys():返回对象自身的所有可枚举属性

Object.assign()只复制对象自身的可枚举属性

7、属性的遍历

es6一共有6种方法可以遍历对象的属性

1)for ...in

2)Object.keys(obj)

返回一个数组

3)Object.getOwnPropertyNames(obj)

4) Object.getOwnPropertySymbols(obj)

5)Reflect.ownKeys(obj)

6) Reflect.enumerate(obj)

8、__proto__属性:用来读取或设置当前对象的prototype对象。

object.setPrototypeOf方法作用和__proto__相同

例子:

let proto = {};
let obj = {x:10};
Object.setPrototypeOf(obj, proto);

proto.y = 20;
proto.z = 40;
obj.x //10
obj.y // 20
obj.z //40

以上代码将prop对象设置为obj对象的原型,所以从obj对象可以读取proto对象的属性 。

object.getPrototypeOf() 用于读取一个对象的prototype属性

function rectangle() {
}
var rec = new rectangle();
Object.getPrototypeOf(rec) === rectangle.prototype // true

9、对象的扩展运算符

rest参数

rest参数复制的是这个值的引用,而不是这个值的副本,即如果一个键的值是复合类型的值(数组,对象,函数),那么rest参数复制的是这个值的引用,而不是这个值的副本

let obj = {a: {b:1}};
let {...x} = obj;
obj.a.b = 2;
x.a.b // 2

扩展运算符

扩展运算符用于取出参数对象的所有可遍历属性,复制到当前对象

let aClone = {...a};
//等同于
let aClone = Object.assign({},a);

 第13章 set和map数据结构

1、set

它类似于数组。但是成员的值都是唯一的,没有重复的值,Set本身是一个构造函数,用来生成Set数据结构

var s = new Set();
[2,3,5,4,5,2,2].map(x=>s.add(x));
for(i of s){
  console.log(i)//2,3,5,4
}

结果表明,set结构不会添加重复的值

Set函数可以接受一个数组作为参数,用于初始化

var set = new Set([1,2,3,4,4]);
[...set] //[1,2,3,4]

2、Set实例的属性和方法

属性:

Set.prototype.constructor; // 构造函数
Set.prototype.size; // 返回Set实例的成员总数

方法:

add(value)

delete(value)

has(value)

clear()

 数组去重:

//数组去重
function dedupe(array) {
  return Array.from(new Set(array));
}
// 数组去重
let arr = [3,5,2,2,5,5];
let unique = [...new Set(arr)];
//[3,5,2]

使用set可以很容易的实现并集,交集,差集

// 数组求交 
util.intersection = function (a, b) {
    let set1 = new Set(a);
    let set2 = new Set(b);
    let Intersect = new Set([...set1].filter(x => set2.has(x)));
    // console.log([...Intersect]);
    return [...Intersect];
}

  

// 数组求差
util.difference = function (a, b) {
    let set1 = new Set(a);
    let set2 = new Set(b);
    let difference = new Set([...set1].filter(x => !set2.has(x)));
    // console.log([...difference]);
    return [...difference];
}
// 并集
let a = new Set([1,2,3]);
let b = new Set([4,3,2]);
let union = new Set([...a,...b]);

3、Map

map结构的目的和基本用法

js的对象本质上是键值对的集合(hash结构)但是只能用字符串作为键

为了解决这个问题,es6提供了Map数据结构,它类似于对象,也是键值对的集合,但是键的范围不限于字符串,各种类型的值(包括对象)都可以当作键

object 结构提供了字符串-值的对应,map结构提供了值-值的对应,是一种更完善的hash结构实现

如果需要键值对的数据结构,map比object更合适

var m = new Map();
var o = {p:"hello world"};

m.set(o,"content");
m.get(o); // "content"
m.has(o); //true
m.delete(o); //true
m.has(o); // false

 第14章 iterator 和 for...of 循环

1、iterator 的概念

 js 的原有的表示“集合”的数据结构,主要是数组和对象,es6又添加了map和set

需要一种统一的接口机制来处理所有不同的数据结构

遍历器是这样一种机制,它是一种接口,为各种不同的数据结构提供统一的访问机制。

任何数据结构只要部署了iterator 接口,就可以完成遍历操作。

iterator 作用有3个:

1)为各种数据结构提供一个统一的简便的访问接口

2)使得数据结构的成员能够按某种次序排列

3)es6创造了一种新的遍历命令,for...of循环,iterator 接口主要供for...of消费

 

Iterator 的遍历过程是这样的。

 

(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

 

(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。

 

(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

 

(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置

 2、for...of循环

 

以上是关于ES6 标准入门的主要内容,如果未能解决你的问题,请参考以下文章

ES6 标准入门-Module

1.《ES6标准入门》(阮一峰)--3.变量的解构赋值

ES6标准入门 论文随笔

《es6标准入门》chapter11中关于Proxy的一个错误例子的纠正

ES6标准入门之正则表达式的拓展

1.《ES6标准入门》(阮一峰)--2.let 和 const 命令