JS的ES6知识点详解

1.let-const块级作用域的补充

const names=["abc","cba","nba"];

for(let  i=0;i

以上for遍历内部的实现是以下这样的:

因为数组有3个元素,所以会产生3个块级作用域。

//第一个块级作用域
{
    let i=0;
    console.log(names[i])
}
//第二个块级作用域
{
    把上次i++的结果赋值新定义的i //注意i++的i和let i不是同一个,js引擎会在内部做一些处理,这里为了方便让我们理解内部的实现
    let i=结果
    console.log(names[i]);
}
//第三个块级作用域
{
    把上次i++的结果赋值给新定义的i
    let i=结果
    console.log(names[i]);
}

所以每个块级作用域的i都不是同一个。

那使用const定义是不是可以实现呢?

如果将上面的代码换一下:

const names=["abc","cba","nba"];

for(const  i=0;i

同样地,因为names有三个元素,所以还是会形成三个作用域。内部实现是这样的:

//第一个块级作用域
{
    const i=0;
    console.log(names[i]);
}
//第二个块级作用域 此时在i++的时候会报错,因为const本质是值不能改变,所以就会抛出错误
{
    //注意i++的i和const i不是同一个,js引擎会在内部做一些处理,这里为了方便让我们理解内部的实现
    将i++的结果赋值给新定义的i  //这一行就开始报错
    const i=结果
}

1.1 for...of 遍历可迭代的数组(或对象)

const names=["abc","cba","nba"];

for(const item of names){ //* 这个数组的元素有三个,所以会形成3个块级作用域
  console.log(item);
}

for...of是将数组或对象每个元素取出来

内部实现:因为数组有3个元素,所以还是会形成3个块级作用域

//第一个块级作用域
{
    const item="abc";
    console.log(item)
}
//第二个块级作用域
{
    const item="cba";
    console.log(item)
}
//第三个块级作用域
{
    const item="nba";
    console.log(item)
}

这里每个块级作用域的item都不是同一个

2.暂时性死区

在ES6中,我们还有一个概念称之为暂时性死区:

  • 它表达的意思是在一个代码块中,使用let、const声明的变量,在声明之前不可以访问的
  • 我们将这种现象称之为 temporal dead zone(暂时性死区,TDZ)(社区)

以下两段代码都形成了暂时性死区,在let、const声明之前,不能进行访问。

var foo="foo";

if(true){
  console.log(foo);
  let foo="abc"; //* Cannot access 'foo' before initialization
}
var foo="foo";
function bar(){
  console.log(foo);
  let foo="abc";
}

bar()

3.var、let、const的选择

对于var的使用:

  • var有其自身的特殊性:作用域提升、在window对象上添加属性、没有块级作用域,这些都是历史遗留问题
  • 其实这是javascript设计之初的一种语言缺陷
  • 目前市场上也在利用这种缺陷出一系列的面试题,来考察大家对JavaScript语言本身以及底层的理解
  • 但是在实际工作中,我们可以使用最新的规范来编写,也就是不再使用var来定义变量了。

对于let、const来说

  • 对于let、const,是目前开发中推荐使用的
  • 我们优先推荐使用const,这样可以保证数据的安全性不会被随意的篡改
  • 只有当我们明确知道一个变量后续会需要被重新赋值时这个时候再使用let
  • 这种在很多其他语言里面也都是一种约定俗成的规范,尽量我们也遵守这种规范。

4.字符串模板基本使用

在ES6之前,如果我们想要使用字符串和动态的变量(标识符)拼接在一起,是非常麻烦和丑陋的(ugly)

ES6允许我们使用字符串模板来嵌入JS的变量或表达式来进行拼接:

  • 首先,我们会使用``符号来编写字符串,称之为 模板字符串
  • 其次,在模板字符串中,我们可以通过${expresion}来嵌入动态的内容
// * 在ES6之前拼接字符串和动态的变量在一起,是非常麻烦的
var name="wjy";
var age=18;
var height=1.6
console.log("我的名字是:"+name+" 年龄是:"+age+" 身高:"+height);

在ES6中提供了模板字符串 ``

如果需要引用动态变量:${变量名}

// *在ES6当中,提供了一个模板字符串 `` 引用动态变量是:${变量名}
const name="wjy";
const age=18;
const height=1.66
console.log(`我的名字是${name} 年龄是:${age} 身高:${height}`)

5.标签模板字符串

模板字符串还有另外一种用法:标签模板字符串 (Tagged Template Literals)

普通JavaScript的函数调用

function foo(m,n){
  console.log(m,n);
}
// * 函数调用最普通的方式
foo(20,39)

如果我们使用标签模板字符串,并且在调用的时候插入其他的变量:

  • 模板字符串被拆分了
  • 第一个元素是数组,是被模板字符串拆分的字符串组合
  • 后面的元素是一个个模板字符串传入的内容
function foo(m,n){
  console.log(m,n);
}
// * 函数调用最普通的方式
foo(20,39)
// * 另外调用函数的方式:标签模板字符串

foo``;//[ '' ] undefined
foo`hello World` //[ 'hello World' ] undefined
const name="wjy";
const age=20;
// * 第一个参数依然是模板字符串的完整字符串,只是被切成了多块,放到了数组中   第二个参数是 模板字符串中,第一个${expression}的expression的值
foo`Hello${age}Wo${name}rld`;//[ 'Hello', 'Wo', 'rld' ] wjy

6.函数的默认参数

在ES5的时候,可以给参数设置值,使用的是 逻辑或:如果前面为真,则返回前面的表达式的值,如果前面为false则将后面的表达式的值返回。

但是存在缺陷:

  • 写起来比较麻烦,阅读性差
  • 存在缺陷:如果传入的值是0或者是"",但还是设置"aaa"或"bbb"
function foo(m,n){
  // * 如果没有给函数传参:那么m,n会是undefined,如果对m、n进行操作时,很容易产生错误
  // * ES5之前是怎么给参数默认值 逻辑或
  /**
   * 缺点:
   * 1.写起来很麻烦 ,并且代码的阅读性比较差
   * 2.这种写起来是有bug 如果传入的是 0 "" ,但是会被设置 为"aaa"或"bbb"
   * 
   */
 m=m|| "aaa";
 n=n||"bbb"
  console.log(m,n);
}
foo();
foo(100);
foo(100,200)

但在ES6中提供了给函数的参数设置默认值,直接参数后面使用="值"

function foo(m="aaa",n="bbb"){
  console.log(m,n);
}

foo();//aaa bbb
foo(100);//100 bbb
foo(100,200);//100 200

上面的代码,转化为ES5是这样的:

"use strict";

function foo() {
  var m =
    arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "aaa";
  var n =
    arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "bbb";
  console.log(m, n);
}

foo(); //aaa bbb

foo(100); //100 bbb

foo(100, 200); //100 200

6.1 函数的默认值

// * 可以传基本数据类型
function foo(m="aaa",n="bbb"){
  console.log(m,n);
}

foo();//aaa bbb
foo(100);//100 bbb
foo(100,200);//100 200

6.7 函数默认值的补充

  1. 默认值也可以和解构一起使用

    function printInfo({name,age}={name:"wjy",age:18}){
      console.log(name,age);
    }
    printInfo()
    
    // * 另外一个写法
    function printInfo({name="wjy",age=20}={}){
      console.log(name,age);
    }
    
    printInfo()
    
  2. 另外参数的默认值我们通常会放到最后(在很多语言中,如果不放到最后其实会报错的)

    • 但是Javascript允许不将其放到最后,但是意味着还是会按照顺序来匹配
  3. 另外默认值会改变函数的length的个数,默认值以及后面的参数都不计算在length之内的

// * 有默认值的函数的length属性
console.log(bar.length); //2

function baz(x=20,y,z){

}
console.log(baz.length);//0

7.函数的剩余参数

ES6中引用了rest parameter,可以将不定数量的参数放入到一个数组中:

如果最后一个参数是以...为前缀,那么它会将剩余的参数放到该参数中,并且作为一个数组

那么arguments和剩余参数有什么区别?

  • 剩余参数只包含没有对应形参的实参,而arguments包含传递给函数的所有实参
  • arguments对象不是一个真正的数组,而剩余参数是一个真正的数组,可以进行数组的所有操作。
  • arguments是早期的ECMAScript中为了方便去获取所有的参数提供的一个数据结构,而剩余参数是ES6提供的,并且希望以此替代arguments

剩余参数必须是 函数的参数的最后一个位置,否则会报错。

function foo(m,n,...args){
  console.log(m,n);//10 20
  console.log(args); //[ 30, 40, 50, 60, 70 ]
}

foo(10,20,30,40,50,60,70)
function bar(...args,m,n){ //*Rest parameter must be last formal parameter
  console.log(args);
  console.log(m);
  console.log(n);
}
bar(1,2,3,4)

8.函数箭头函数的补充

  • 箭头函数是没有显式原型的,所以不能作为构造函数
  • 箭头函数没有this,箭头函数的this会去上层作用域查找,如果还没有找到,会继续往上查找。直到全局作用域中
  • 箭头函数没有arguments
var bar=()=>{
  
}
console.log(bar.prototype);//undefined
console.log(new bar());//* bar is not a constructor

9.展开语法(Spread syntax)

  • 可以在函数调用/数组构造时,将数组表达式或者string在语法层面展开
  • 还可以在构造字面量对象时,将对象表达式按key-value的方式展开
const names=["abc","wjy","nba"];

// * 1.函数调用时
function foo(x,y,z){
  console.log(x,y,z);
}
foo.apply(null,names);//这种方式也可以,但是阅读性非常差
foo(...names)
let str="wjy";
foo(...str)

// * 2.构造数组时
const newNames=[...names]

//*  3. ES2018 (ES9)构造 字面量对象
let info={
  name:"wjy",
  age:20,
  height:160
}
let newInfo={...info,...names}; //* 数组展开在对象中,key是对应的索引值
console.log(newInfo);

// * 展开运算符其实是一个浅拷贝

9.1 展开运算符的浅拷贝

const info={
  name:"wjy",
  friend:{
    name:"kobe"
  }
}
// * 浅拷贝的是拷贝的对象的第一层属性的值,如果拷贝的对象的属性的值引用类型,其实在拷贝的对象中对应的属性的引用的是同一片内存空间
const newInfo={...info}
newInfo.friend.name="hyz"
console.log(info);

10.数值的表示

在ES6中规范了二进制和八进制的写法:

  • 二进制:以0b开头
  • 八进制:以0O开头
  • 十六进制:以0x开头
let num1=100;//默认是10进制
let num2=0b100;//二进制
let num3=0O100;//八进制
let num4=0x100;//十六进制

console.log(num1,num2,num3,num4);//100 4 64 256


// * 大的数值(在ES2021 ES12中),允许使用下划线进行连接
// const num=10000000000;//这种可读性非常差
const num=10_000_000_000;
console.log(num);

11.Symbol的基本使用

Symbol是ES6新增的一个基本的数据类型,翻译为符号。

那么为什么需要Symbol呢?

  • 在ES6之前,对象的属性都是字符串形式,那么很容易造成属性名的冲突
  • 比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是在我们不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖它内部的某个属性
  • 比如我们前面实现讲apply、call、bind实现时,我们给其中添加一个fn属性,那么如果它内部原来有一个fn属性了呢?
  • 如果在开发中我们使用了混入,那么混入中出现了同名的属性,必然有一个会被覆盖掉。

Symbol就是为了解决上面的问题,用来生成一个独一无二的值

  • Symbol的值是通过Symbol函数来生成的,生成后可以作为属性名
  • 也就是在ES6中,对象的属性名就可以使用字符串,也可以使用Symbol值

Symbol即使多次创建值,它们也是不同的:Symbol函数每次创建出来的值都是独一无二的

我们也可以在创建Symbol值的时候传入一个描述description:这个是ES2019(ES10)新增的特性

11.1 Symbol作为属性名

// * 3.Symbol作为key
// * 写法1
const obj2={
  [s1]:"abc",
  [s2]:"wjy"
}
console.log(obj2);

// * 新增:ES2019 ES10新增可以向Symbol传入一个description
obj2[s3]="hyz"
console.log(obj2);

// * 新增
Object.defineProperty(obj2,Symbol("s4"),{
  value:"s4",
  configurable:true,
  enumerable:true,
  writable:true
})

// * 获取:只能通过[]获取,不能通过.语法获取
console.log(obj2[s1]);
console.log(obj2.s1);//* undefined 这样是获取不到的,因为通过.获取的时候,会根据后面的名字去对象找对象的属性名为这个的值
// * 4.使用Symbol作为key,在遍历/Object.keys是获取不到Symbol属性值的
// * 需要通过Object.getOwnPropertySymbols来获取所有的Symbol的key
console.log(Object.keys(obj2));//[]
console.log(Object.getOwnPropertyNames(obj2));//[]
console.log(Object.getOwnPropertySymbols(obj2));//[ Symbol(), Symbol(), Symbol(hyz), Symbol(s4) ]

let sKeys=Object.getOwnPropertySymbols(obj2)

for(const item of sKeys ){
  console.log(obj2[item]);
}

11.2 相同的key生成相同的Symbol

如果想根据相同的key创建相同的Symbol可以使用Symbol.for(key)

可以通过Symbol.keyFor获取对应的key

// * 5. Symbol.for(key) :如果想要创建一样的Symbol值:通过相同的key创建相同的Symbol
const sa=Symbol.for("aaa");
const sb=Symbol.for("aaa");
console.log(sa==sb);//true

// * 获取key
const key=Symbol.keyFor(sa);
console.log(key);

12.总结

ES6知识点详解.png

版权声明:
作者:主机优惠
链接:https://www.techfm.club/p/49981.html
来源:TechFM
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>