ES6-cnblog
1.var、let、const关键字
var声明变量有以下特性:
-
变量提升,可以先使用,再声明;
-
函数作用域和全局作用域:
function print(){ if(true){ var a = 10,sum = 0; for(var i=0;i<a;i++){ sum+=i; } console.log(sum); } //在这里同样可以访问到sum,也就是说在这个函数内,变量是共用的 console.log(sum); } -
允许重复声明:
var a = 1; var a = 2; -
全局变量会挂载到全局window;
let关键字有下面特性:
-
不拥有变量提升,即不能先使用再声明;
-
不能在块级作用域内重复声明相同变量;
-
拥有块级作用域:
function getSum(n){ let sum = 0; for(let i=1;i<=n;i++){ sum+=i; } //报错,因为i是用let声明,所以不能在{}外访问 console.log(i); return sum; } -
不能挂载到window;
暂时性死区(TDZ,tempoary dead zone)
当使用let声明变量时,这个变量会绑定这个块级作用域,即一进入这个块,变量就已经存在了,但是只有直到声明变量这一行才可以使用变量,否则就会报错。
function func(){
var tmp;
if(true){
//TDZ开始
tmp=3;
tmp+=2;
let tmp;//TDZ结束
}
}
function func(x=y,y=2){
return [x,y];
}
func();//这也是同样会报错的,因为y在声明之前就已经被使用了,所以会报错,即x=y处于TDZ
var x=x;✔
let x=x;❌
其实感觉说这么多,我觉得前端一开始就是规范不严格,如果按照变量只能在被声明之后才可以使用,哪里有这么多问题,在强类型语言中,这就是最基本的。
const关键字:
const关键字声明的变量是变量绑定,修饰普通变量就相当于常量,不能被修改,修饰引用类型时,绑定的这个引用对象不能改变,但是可以修改对象的属性,同时声明时必须赋初值。
const a = 10;
a = 8;//❌
const arr = [1,2,3];
arr.push(4);//✔
arr = [20,30];//❌,改变了引用对象
总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
2.函数
1.普通函数定义
//1.传入的参数都不能添加类型修饰,即不能加let、var、const等,但是可以有默认初始值
//2.不需要写返回值类型,可以返回或者不返回
//求区间[a,b]的和,再加上一个常数c,c有默认值
function(a,b,c=10) 函数名{
let sum = 0;
for(let i=a;i<=b;i++){
sum+=i;
}
return sum+c;
}
上面这种函数定义方式存在以下问题:
- 函数提升,即可以先使用,后定义;
2.函数表达式
函数引用方式
-
可以直接在html文件中的script标签处写函数,然后直接在script中使用函数;
-
将函数写在js文件中,之后在html页面的head标签中加入
<script src="./js/demo1.js"></script>这样就可以将其导入到当前html文件中使用,但是这样会使其变成全局函数,容易造成命名冲突;
-
在大型项目中,我们一般使用模块导入的方式:
//./js/demo1.js export function getSum(n){ let sum = 0; for(let i=1;i<=n;i++){ sum+=i; } return sum; }在html页面中使用时,必须在body标签中的script标签处使用参数type="module":
//demo1.html <script type="module"> import {getSum} from "./js/demo1-export.js" console.log(getSum(5)); </script>需要的属性单独导入即可。
3.解构赋值
3.1数组的解构赋值
实际上就是模式匹配,比如下面例子:
let [a,b,c]=[1,2,3]
let [a,[b],,c]=[1,[2,3],4,5]
//a=1
//b=2
//c=5
//可以看到这个会按照模式匹配,然后就行赋值
//其中b还是一个不完全解构
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
demo,实现斐波拉契数列惰性求值:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Generator 生成器函数,实现惰性求值</title>
</head>
<body>
<script>
// 斐波那契数列生成器
function* fibs() {
let a = 0;
let b = 1;
while (true) { // 无限循环,生成无穷斐波那契序列
yield a; // 暂停,向外产出当前a的值
[a, b] = [b, a + b]; // 数组解构更新a、b
}
}
//获取到当前用例迭代器
const iterator = fibs();
let count = 10, i = 0;
while(i<count){
console.log(i+1+":"+iterator.next().value);
i++;
}
</script>
</body>
</html>
只要某种数据结构具有具有Iterator接口,都可以使用数组形式的解构赋值。
上面fibs是一个Generator函数,原生具有Iterator接口。
默认值
对于解构赋值时,我们可以指定默认值,比如:
let [foo=true]=[];
//foo true
let [x,y='a']=['b'];
//x='b',y='a'
let [x,y='b']=['a',undefined];
//x='a',y='b'
let [x,y='b']=['a',null];
//x='a',y=null
可以看出:
- es6内部使用严格相等运算符(===)来判断一个位置是否有值,所以只有当一个位置为
undefined时,才会使用默认值;
默认值还可以是一个表达式,这个表达式是惰性求值的,即只有在用到的时候,才会求值。
function f(){
console.log('aaaa');
}
let [x=f()]=[1];
console.log(x);
上面的f()根本不会执行,因为1赋值给他了,只有当没有值给他或者是undefined时,f函数才会执行。
3.2对象的解构赋值
对象的结构赋值不依赖位置关系,依赖对象属性名,如果没有匹配,那么找不到赋值。
let {foo,bar}={foo:'aaa',bar:'bbb'};
//foo 'aaa'
//bar 'bbb'
let {zxy} = {foo:'aaa'};
//zxy undefined
如果变量名与属性名不一致,必须写成下面这样。
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'
也就是说,如果属性名后面没有给对应的变量名时,引擎会直接使用和属性名相同的变量名。
let {foo:baz} = {foo:'ok'};
//foo undefined
//baz 'ok'
属性名只是一个模式,用于匹配,最后赋值还是给到后面的变量名。
对象结构赋值也是可以使用默认值的,和数组的解构赋值相似。
同时在进行解构时,如果等号右边是基本类型,那么会先转化成包装数据类型,由于undefined和null不具有包装数据类型,所以他们出现在等号右边时,会报错。
下面这种情况也要注意:
let x=5;
{x}={3};
//js引擎会将{x}解析为块级作用域,导致结构失败
({x}={3});
//这样就不会失败了,可以先加一个括号
3.3结构赋值的用处
-
交换变量的值:
let x=1,y=2; [x,y]=[y,x]; -
从函数返回多个值:
function f(){ return [1,2,3]; } -
函数参数列表;
-
解析Json数据;
-
函数参数的默认值;
-
遍历map:
for([key,value] of map){ console.log(key+" is "+value); } for([key] of map){ } for([,value] of map){ } -
导入其他模块方法;
4.字符串常用API
1.模板字符串
可以支持换行、插值语法。
const name = '李四';
let str = `你好,${name}!
现在你吃饭了吗?`
console.log(str);
/*
你好,李四!
现在你吃饭了吗?
*/
2.使用includes判断是否存在子串
let str="Hello es6";
console.log(str.includes("Hello"));//true
console.log(str.includes("es6"));//true
3.startWith(str)
let str="hhhoo";
console.log(str.startWith('hh'));//true
4.endWith(str)
5.repeat(n),将一个字符串重复n次
"abc".repeat(3);//abcabcabc
6.字符串补全长度
"5".padStart(2,"0"); // "05" 头部补全
"5".padEnd(3,"x"); // "5xx" 尾部补全
7.es6字符串实现迭代器接口,可以使用for of
const str = "😀a";
for(let char of str){
console.log(char);
}
8.String静态方法raw
// 不用写双反斜杠
String.raw`C:\user\demo`
9.标签模板
会有一个伪参数arguments=[[静态文本],插值1,插值2...]
function tag(literals,...vaules){
let result="";
for(let i=0;i<values.length;i++){
result+=literals[i]+values[i];
}
result+=literals[values.length];
return result;
}
const name="lizhe",age=21;
tag`Hello ${name},I'm ${age} years old.`;
//等价于
tag(["Hello ",",I'm "," years old."],"lizhe",21);
标签模板的一个常用处——转义,防止恶意攻击。
我们知道<被浏览器读到之后,会被浏览器当作是一个标签的开始,会创建对应的dom元素,比如下面的语句:
<script>
const evilText = `<img src=x onerror="alert('xss')">`;
<p>评论:${evilText}</p>
</script>
上面这段js代码被浏览器创建一个img属性的dom元素,当找不到src时,执行错误事件alert("xss"),这样用户输入的一段文本就可以对页面进行更改了,但是实际是不允许这样的,所以标签模板可以很好的处理这个,我们对用户输入的插值变量进行转义,这样转换后的文本变成:
<p>评论:<img src=x onerror="alert('xss')"></p>
当浏览器读到这样的文本之后,之后对某些字符进行转义,最后得到
<img src=x onerror="alert('xss')">
这样的文本,而不会去创建dom元素了。
这样就可以很好的将用户输入的进行处理,开发者写的静态代码肯定是安全的,我们需要处理的就是用户输入的,一般来说用户肯定是展示数据,不能写这样的脚本。标签模板可以将静态文本和插值分开,这样非常好进行处理。
demo:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模板标签,防止恶意代码</title>
</head>
<body>
<script>
function escapeHTML(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function safeHtml(literals, ...values) {
let res = "";
for (let i = 0; i < literals.length; i++) {
res += literals[i];
if (values[i]) {
res += escapeHTML(values[i]);
}
}
return res;
}
// const evilText = '<img src=x onerror="alert(\'xss\')">';
const evilText = `<img src=x onerror="alert(\'xss\')">`;
const template = safeHtml`<p>评论:${evilText}</p>`;
console.log(template);
document.body.innerHTML = template;
</script>
</body>
</html>
Vue/React 插值 {{ msg }} 默认就是这套转义逻辑防止 XSS。
介绍一下XSS:
XSS 全称:Cross-Site Scripting,跨站脚本攻击
因为 CSS 已经占了缩写,所以取名 XSS。
- 盗取用户 Cookie(最经典),冒充账号登录
- 伪造页面、诱导输入账号密码
- 篡改网页内容
- 调用前端接口,以当前用户身份发起操作
第一点确实常见,写过后端登录的朋友肯定知道,如果使用cookie保存SessionId的话,那么盗取cookie之后,就可以伪造登录请求,把cookie换成盗取用户的,这样在进行认证的时候,后端就会通过认证,实现冒充登录。
第二个也是很常见的,经常可以听到什么说钓鱼执法这种,就是点击之后会伪造一个页面,输入个人信息之后就会发送到远端。
5.rest参数和拓展运算符...
rest参数主要用在函数参数列表处,和Java中的可变参数差不多,rest接收多个参数然后转换成数组对象。
注意点:
- arguments是类数组对象,不能直接调用数组API,只用将其转换成数组之后才能使用,而rest是数组对象;
- rest参数必须放在参数列表最后,且只能有一个;
拓展运算符...,和rest相反,这个是用来将数组或者对象解析为一项一项的。
普通未实现迭代器的对象不能用在函数参数列表。
6.箭头函数
形式:
const add=(a,b)=>a+b;//返回a+b的值
使用语法:
-
如果参数只有一个,那么可以省略括号;
const double=x=>x*2; -
如果代码只有一行,不用写return和大括号,会自动返回;
-
如果代码有多行,需要加大括号,并手动返回;
-
如果返回的是对象字面量,那么需要加上一个
(),以此区分代码块和对象;
注意点:
-
没有自身的
this,会依赖所在作用域的this;比如这样的场景:
//普通函数 const A={ name:'A', fn:function(){ console.log(this.name); } } A.fn();//A const B={ name:'B' } A.fn.call(B);//B也就是说普通函数的this指向的是调用时,而箭头函数永远指向的是定义时的this。
//箭头函数 const name='window'; const A={ name:'A', sayHello():()=>{ console.log(this.name); } } console.log(A.sayHello());//window,也就是说这里sayHello所处作用域在window const B={ name:'B' }改造一下,我们使用函数包裹箭头函数:
//箭头函数 const name='window'; const A={ name:'A', sayHello:function(){ let s=()=>console.log(this.name); return s; } } const fn1=A.sayHello(); console.log(fn1());//A const B={ name:'B' } const fn2=A.sayHello.call(B); console.log(fn2());//A,这就说明this永远指向定义时的this -
不能使用new创建对象;
-
没有arguments对象,只能使用rest;
-
不能使用yield,不能用作Generator函数;
-
没有prototype属性;
7.Promise
这是一个用来解决回调地狱的机制。
promise意为承诺,表示我一定会在未来某个时间通知执行的结果。所以这也是一个异步机制,即不会阻塞主线程代码的执行。promise实现更像状态机思想,根据不同状态,决定调用不同函数(被调用的函数我们也称为回调函数)。
promise对象有三种状态:pending(提交中)、fulfilled(成功完成)、rejected(失败)。
构建一个Promise实例:
//获取一个实例
const promise=new Promise(一个函数(resolve,reject)=>{
let flag=true;
if(flag){
resolve("成功");
}else{
reject("失败");
}
});
function fn(flag,str1,str2){
return new Promise((resolve,reject)=>{
if(flag){
resolve(str1);
}else{
reject(str2);
}
});
}
fn(true,"成功").then(res=>{
console.log(res);
}).catch(res=>{
console.log(res);
}).finally(()=>{
console.log("执行完成");
});
通过实例方法进行注册回调函数,then(成功时的回调函数,(失败时的回调函数)).then.then.catch(失败时的回调函数).finally()
//返回一个promise对象
getUserInfo(userId)
.then(user => getOrderList(user.id))
.then(orders => getGoodsList(orders[0].id))
.then(goods => console.log('最终获取到商品:', goods))
.catch(err => console.error('任何一步出错都会在这里捕获:', err));
promise静态API
-
resolve(),返回一个状态变为fulfilled的promise对象;
-
reject(),返回一个状态变为rejected的promise对象;
-
promise.all(),all参数列表可以是一个元素全为promise对象的数组,只有当所有promise元素都成功时,promise.all才会将当前promise对象变为fulfilled状态,否则就是rejected,也就是只有所有成功才算成功,否则就是失败。
适用场景:多个异步任务并行执行,需要等待所有任务完成后再处理结果。
-
Promise.race(),参数列表中的promise对象处于竞争状态,谁先完成就接受谁的返回结果,不管成功或者失败;
const p4=new Promise(resolve=>setTimeout(resolve(5),1000)); const p5=new Promise(resolve=>setTimeout(resolve(2),2000)); Promise.race([p4,p5]) .then(res=>{ console.log(res); });适用场景:请求超时控制。
-
Promise.allSettled(),不管成功失败,所有promise都返回结果。
-
Promise.any()
Promise.any([p6,p8,p9]) .then(res=>{ console.log(res); });有一个执行成功就返回。
适用场景:多个备用接口,只要有一个成功就可以。
async/await 是 ES2017 引入的语法糖,它让异步代码看起来完全像同步代码,是 Promise 的最佳实践。
// 用 async 标记函数
async function fetchData() {
try {
// 用 await 等待 Promise 完成
const user = await getUserInfo(userId);
const orders = await getOrderList(user.id);
const goods = await getGoodsList(orders[0].id);
console.log('最终获取到商品:', goods);
} catch (err) {
console.error('出错了:', err);
}
}
注意:async/await 只是 Promise 的语法糖,它的底层依然是 Promise。
ES6——Promise一文彻底搞懂 Promise:从原理到手写实现,面试再也不怕 一、为什么我们需要 Promise - 掘金
8.模块化
使用到的关键字为export和import,通过export导出函数或者变量,通过import进行导入。
一个js文件只允许一个默认导出,这样其他文件引用这个文件的模块时,即使不知道模块名,也可以导入,而且在导入时,还可以使用任意命名。
一文搞懂ES6中Module和使用场景一、介绍 模块,(Module),是能够单独命名并独立地完成一定功能的程序语句的集 - 掘金
9.Object静态方法
-
Object.keys(obj)
作用:获取对象所有自身可以枚举属性名,返回属性名数组;
const user = {username:"张三",age:"18"}; const keys = Object.keys(user); console.log(keys);//["username","age"] -
Object.values(obj)
作用:获取对象所有自身可枚举属性值,返回值数组
const values = Object.values(user); console.log(values);//["张三","18"] -
Object.entries (obj) ⭐重点
作用:获取对象键值对,返回
[key,value]二维数组const entries = Object.entries(user); console.log(entries);//[["username","张三"],["age","18"]]通常是结合for of来使用:
for(let[key,value] of Object.entries(user)){ let str=`key is ${key},value is ${value}.`; console.log(str); } //key is username,value is 张三. //key is age,value is 18. -
反向操作fromEntries,将entries变为对象,还可用于将map转成对象
const arr = [["name","李四"],["age",20]]; const obj = Object.fromEntries(arr); console.log(obj); // {name:"李四",age:20}适用场景:快速处理 URL 参数、Map 转对象
重要约束(三者通用)
- 只读取自身属性,不遍历原型链上属性
- 只获取 可枚举属性(enumerable:true)
- 如果传入数组:会把下标当作 key
console.log(Object.keys([3,4]));//['0','1']
对比 for...in
for...in:遍历自身 + 原型链可枚举属性,需要搭配hasOwnProperty过滤- Object.keys/values/entries:仅自身属性,业务代码更安全,推荐优先使用
简易总结
- keys → 拿到所有键
- values → 拿到所有值
- entries → 拿到 [键,值]
- fromEntries → [键,值] 数组变回对象

浙公网安备 33010602011771号