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;
}

上面这种函数定义方式存在以下问题:

  1. 函数提升,即可以先使用,后定义;

2.函数表达式

函数引用方式

  1. 可以直接在html文件中的script标签处写函数,然后直接在script中使用函数;

  2. 将函数写在js文件中,之后在html页面的head标签中加入

    <script src="./js/demo1.js"></script>
    

    这样就可以将其导入到当前html文件中使用,但是这样会使其变成全局函数,容易造成命名冲突;

  3. 在大型项目中,我们一般使用模块导入的方式:

    //./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结构赋值的用处

  1. 交换变量的值:

    let x=1,y=2;
    [x,y]=[y,x];
    
  2. 从函数返回多个值:

    function f(){
        return [1,2,3];
    }
    
    
  3. 函数参数列表;

  4. 解析Json数据;

  5. 函数参数的默认值;

  6. 遍历map:

    for([key,value] of map){
        console.log(key+" is "+value);
    }
    for([key] of map){
        
    }
    for([,value] of map){
        
    }
    
  7. 导入其他模块方法;

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>评论:&lt;img src=x onerror=&quot;alert(&#039;xss&#039;)&quot;&gt;</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, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;')
                .replace(/"/g, '&quot;')
                .replace(/'/g, '&#039;');
        }

        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。

  1. 盗取用户 Cookie(最经典),冒充账号登录
  2. 伪造页面、诱导输入账号密码
  3. 篡改网页内容
  4. 调用前端接口,以当前用户身份发起操作

第一点确实常见,写过后端登录的朋友肯定知道,如果使用cookie保存SessionId的话,那么盗取cookie之后,就可以伪造登录请求,把cookie换成盗取用户的,这样在进行认证的时候,后端就会通过认证,实现冒充登录。

第二个也是很常见的,经常可以听到什么说钓鱼执法这种,就是点击之后会伪造一个页面,输入个人信息之后就会发送到远端。

5.rest参数和拓展运算符...

rest参数主要用在函数参数列表处,和Java中的可变参数差不多,rest接收多个参数然后转换成数组对象。

注意点:

  • arguments是类数组对象,不能直接调用数组API,只用将其转换成数组之后才能使用,而rest是数组对象;
  • rest参数必须放在参数列表最后,且只能有一个;

拓展运算符...,和rest相反,这个是用来将数组或者对象解析为一项一项的。

普通未实现迭代器的对象不能用在函数参数列表。

6.箭头函数

形式:

const add=(a,b)=>a+b;//返回a+b的值

使用语法:

  1. 如果参数只有一个,那么可以省略括号;

    const double=x=>x*2;
    
  2. 如果代码只有一行,不用写return和大括号,会自动返回;

  3. 如果代码有多行,需要加大括号,并手动返回;

  4. 如果返回的是对象字面量,那么需要加上一个(),以此区分代码块和对象;

注意点:

  1. 没有自身的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
    
  2. 不能使用new创建对象;

  3. 没有arguments对象,只能使用rest;

  4. 不能使用yield,不能用作Generator函数;

  5. 没有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

  1. resolve(),返回一个状态变为fulfilled的promise对象;

  2. reject(),返回一个状态变为rejected的promise对象;

  3. promise.all(),all参数列表可以是一个元素全为promise对象的数组,只有当所有promise元素都成功时,promise.all才会将当前promise对象变为fulfilled状态,否则就是rejected,也就是只有所有成功才算成功,否则就是失败。

    适用场景:多个异步任务并行执行,需要等待所有任务完成后再处理结果。

  4. 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);
            });
    

    适用场景:请求超时控制。

  5. Promise.allSettled(),不管成功失败,所有promise都返回结果。

  6. 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.模块化

使用到的关键字为exportimport,通过export导出函数或者变量,通过import进行导入。

一个js文件只允许一个默认导出,这样其他文件引用这个文件的模块时,即使不知道模块名,也可以导入,而且在导入时,还可以使用任意命名。

一文搞懂ES6中Module和使用场景一、介绍 模块,(Module),是能够单独命名并独立地完成一定功能的程序语句的集 - 掘金

9.Object静态方法

  1. Object.keys(obj)

    作用:获取对象所有自身可以枚举属性名,返回属性名数组;

    const user = {username:"张三",age:"18"};
            const keys = Object.keys(user);
            console.log(keys);//["username","age"]
    
    
  2. Object.values(obj)

    作用:获取对象所有自身可枚举属性值,返回值数组

    const values = Object.values(user);
            console.log(values);//["张三","18"]
    
  3. 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.
    
    
  4. 反向操作fromEntries,将entries变为对象,还可用于将map转成对象

    const arr = [["name","李四"],["age",20]];
    const obj = Object.fromEntries(arr);
    console.log(obj); // {name:"李四",age:20}
    

    适用场景:快速处理 URL 参数、Map 转对象

重要约束(三者通用)

  1. 只读取自身属性,不遍历原型链上属性
  2. 只获取 可枚举属性(enumerable:true)
  3. 如果传入数组:会把下标当作 key
console.log(Object.keys([3,4]));//['0','1']

对比 for...in

  • for...in:遍历自身 + 原型链可枚举属性,需要搭配 hasOwnProperty 过滤
  • Object.keys/values/entries:仅自身属性,业务代码更安全,推荐优先使用

简易总结

  • keys → 拿到所有键
  • values → 拿到所有值
  • entries → 拿到 [键,值]
  • fromEntries → [键,值] 数组变回对象
posted @ 2026-08-03 22:43  alij  阅读(6)  评论(0)    收藏  举报