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 函数默认值的补充
-
默认值也可以和解构一起使用
function printInfo({name,age}={name:"wjy",age:18}){ console.log(name,age); } printInfo() // * 另外一个写法 function printInfo({name="wjy",age=20}={}){ console.log(name,age); } printInfo()
-
另外参数的默认值我们通常会放到最后(在很多语言中,如果不放到最后其实会报错的)
- 但是Javascript允许不将其放到最后,但是意味着还是会按照顺序来匹配
-
另外默认值会改变函数的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);
共有 0 条评论