JSES6知识大全

ES6知识

字面量增强的写法的三种写法

ES6中对 对象字面量 进行了增强,称之为 Enhanced object literals(增强对象字面量)。

字面量的增强主要包括下面几部分:

  1. 属性的简写:Property Shorthand
  2. 方法的简写:Method Shorthand
  3. 计算属性名:Computed Property Names
var name = "why";
var age = 18;

// 01_字面量增强的写法
// var obj = {} ==  var obj =new Object()
var obj = {
  // 1.property shorthand(属性的简写)
  name, //原形 name(key值) :name (变量名)  当对象字面量中得key值和定义变量的名称相同时 可以简写
  age,

  // 2.method shorthand(方法的简写)
  // 原形es5写法
  foo: function () {
    // obj对象 ,普通函数和调用的关系有关,和定义的位置无关
    console.log(this);
  },
  // es6写法简写语法糖
  bar() {
    // obj对象
    console.log(this);
  },
  // 箭头函数 上层作用域 这是箭头函数
  baz: () => {
    console.log(this);
  },

  // 3.computed property name(计算属性名);
  // (计算属性名)es6写法
  [name + 12311]: "hehehehe",
};

// obj. baz(); //箭头函数 上层作用域  全局
// obj.bar(); //obj对象
// obj.foo(); //obj对象
// (计算属性名)es6之前写法
obj[name + 123] = "hahaha";
console.log(obj);

解构

我们可以划分为:数组的解构和对象的解构

1. 解构-数组的解构

var names = ["abc", "cba", "nba"];
// var item1 = names[0]
// var item2 = names[1]
// var item3 = names[2]

// 对数组的解构: [] 是按照index索引
var [item1, item2, item3] = names;
console.log(item1, item2, item3);

// 解构后面的元素
var [, , itemz] = names;
console.log(itemz);

// 解构出一个元素,后面的元素放到一个新数组中
var [itemx, ...newNames] = names;
console.log(itemx, newNames);

// 解构的默认值
var [itema, itemb, itemc, itemd = "aaa"] = names;
console.log(itemd);

2. 对象的解构

var obj = {
  name: "why",
  age: 18,
  height: 1.88,
};

// 对象的解构: {} 是按照key属性名 键名解构
var { name, age, height } = obj;
console.log(name, age, height);
// 解构单个
var { age } = obj;
console.log(age);
// 重命名解构 ==将name的值赋值给newName 打印newName
var { name: newName } = obj;
console.log(newName);
// 解构给默认值
var { address: newAddress = "广州市" } = obj;
console.log(newAddress);

function foo(info) {
  console.log(info.name, info.age);
}

foo(obj);

// 函数解构
// 对过来的参数解构 这里传过来的参数是obj 就是对obj进行解构 解构出来里面的name , age
function bar({ name, age }) {
  console.log(name, age);
}

bar(obj);

let、const、var的区别

(1)块级作用域:块作用域由 { }包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:
● 内层变量可能覆盖外层变量
● 用来计数的循环变量泄露为全局变量
(2)变量提升:var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。
(3)给全局添加属性:浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。
(4)重复声明:var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。
(5)暂时性死区:在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。
(6)初始值设置:在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
(7)指针指向:let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。
在这里插入图片描述

let-const的基本使用

// var foo = "foo"
// let bar = "bar"

// const constant(常量/衡量)
// const name = "abc"
// name = "cba"

// 注意事项一: const本质上是传递的值不可以修改
// 但是如果传递的是一个引用类型(内存地址), 可以通过引用找到对应的对象, 去修改对象内部的属性, 这个是可以的
// const obj = {
//   foo: "foo"
// }

/// obj = {}
// obj.foo = "aaa"
// console.log(obj.foo)

// 注意事项二: 通过let/const定义的变量名是不可以重复定义
// var foo = "abc"
// var foo = "cba"

let foo = "abc";
// SyntaxError: Identifier 'foo' has already been declared
// let foo = "cba"

console.log(foo);

const对象的属性可以修改吗

const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。对于基本类型的数据(数值、字符串、布尔值),其值就保存在变量指向的那个内存地址,因此等同于常量。

但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的内存地址,保存的只是一个指针,const只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。

const定义的对象保证的并不是变量的值,而是变量指向的内存地址不能改动;

而对于基本数据类型来说,用const定义基本数据类型,等同于常量;对于引用数据类型来说,变量指向的是数据的内存地址,保存的是一个指针(引用地址)

const obj = { name: "张三", age: 18 };
      obj.name = "李四";
      console.log(obj); //{name: '李四', age: 18}

      const obj = { name: "qqq" };
      console.log(obj);// 报错 Uncaught SyntaxError: Identifier 'obj' has already been declared

let-const的作用域提升

// console.log(foo)
// var foo = "foo"

// Reference(引用)Error: Cannot access 'foo' before initialization(初始化)
// let/const他们是没有作用域提升 作用域提升: 能提前被访问
// foo被创建出来了, 但是不能被访问(js内部处理设置的)
// 总结:是let、const没有进行作用域提升,但是会在解析阶段被创建出来。 但是不能被访问(因为js内部处理设置的不能访问)
console.log(foo);
let foo = "foo";

let-const和window关系

在这里插入图片描述
在这里插入图片描述

// var foo = "foo"
// var message = "Hello World"

// console.log(window.foo)
// console.log(window.message)

// window.message = "哈哈哈"
// console.log(message)
// es6之前 全局作用域下定义变量 会添加到我window上 GO = window
// es6以后 var声明变量虽然会和window同步 但是go 浏览器和window已经不是同一个对象了

let foo = "foo";

console.log(window.foo); //undefined

ES5作用域的理解

// 声明对象的字面量 是键值对 本身就是个对象,没有作用域
// var obj = {
//   name: "why"
// }

// ES5中早期是没有块级作用域  --(块级作用域是外面无法访问的)
// 块代码(block code) 里面写表达式
// {
//    声明一个变量
//   var foo = "foo"
// }

// console.log(foo)

// 在ES5中只有两个东西会形成作用域
// 作用域是外面无法访问的内部的属性,我们自己声明的东西只能内部自己访问
// 1.全局作用域 全局声明的变量
// var aa = 111
// 2.函数作用域
// function foo() {
//   var bar = "bar"
// }

// console.log(bar) //访问不到 ,函数有自己的作用域
// 下面代码一共三个作用域 内部的作用域可以沿着外面作用域链一层层查找,但是外面的作用域无法访问内部的作用域里面的东西
// 1.全局作用域
// 2.foo的函数作用域
function foo() {
  // 3.demo的函数作用域
  function demo() {}
}

ES6的块级作用域

// ES6的代码块级作用域 对var的声明依然是无效的
// 对let/const/function/class声明的类型是有效,但是var依然是无效的
//  {}块级作用域
{
  let foo = "why";
  function demo() {
    console.log("demo function");
  }
  class Person {}
}

console.log(foo); // foo is not defined
// 函数有点特殊 如果我们的运行环境只支持es6的话,function是有块级作用域,但是浏览器为了兼容以前的代码, 让这里的function是没有块级作用域---
// 但是函数里面定义变量的变量也是无法访问 除非表达式不写变量类型---不声明变量 只要写了不管是var 函数let/const 外面都无法访问
// 原因:不同的浏览器有不同实现的(大部分浏览器为了兼容以前的代码, 让function是没有块级作用域)
// demo()
var p = new Person(); // Person is not defined  --在作用域外面是无法访问内部的块级作用域里面的东西

if-switch-for块级代码

{
}

// if语句的代码就是块级作用域
// if (true) {
//   var foo = "foo"  //var 可以访问
//   let bar = "bar"    //let不可以访问 块级作用域
// }

// console.log(foo)
// console.log(bar)

// switch语句的代码也是块级作用域
// var color = "red"

// switch (color) {
//   case "red":
//     var foo = "foo"  //var 可以访问
// let bar = "bar"  //let不可以访问 块级作用域
// }

// console.log(foo)
// console.log(bar)

// for语句的代码也是块级作用域
// for (var i = 0; i < 10; i++) {  //var 可以访问
//    console.log("Hello World" + i)
// }

// console.log(i)

for (let i = 0; i < 10; i++) {
  //let不可以访问 块级作用域
}

console.log(i);

块级作用域的应用场景

const btns = document.getElementsByTagName("button");
// 闭包方式解决 :通过多一层函数 形成多一层的作用域 立即执行函数 通过参数n 函数内部的n去访问函数外部n的,形成闭包
//  var 没有块级作用域
// for (var i = 0; i < btns.length; i++) {
//   (function(n) {
//     btns[i].onclick = function() {
//       console.log("第" + n + "个按钮被点击")
//     }
//   })(i)
// }

// console.log(i)
//  let 有块级作用域 这里的内部的i就是访问外部块级作用域的i
for (let i = 0; i < btns.length; i++) {
  btns[i].onclick = function () {
    console.log("第" + i + "个按钮被点击");
  };
}

// console.log(i)

块级作用域的补充

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

// 不可以使用const 主要原因是后面进行++操作,修改const常量的值,所以报错
for (let i = 0; i < names.length; i++) {
  console.log(names[i]);
}

// {
//   let i = 0
//   console.log(names[i])
// i = 0 +1
// }

// {
//   let i = 1
//   console.log(names[i])
// i = 1 +1
// }

// {
//   let i = 2
//   console.log(names[i])
//   退出循环
// }

// for...of: ES6新增的遍历数组(对象)
// 这里是可以用const 因为每次循环都是新的作用域,切在每个作用域里面没有修改const值
for (const item of names) {
  console.log(item);
}

// {
//   const item = "abc"
//   console.log(item)
// }

// {
//   const item = "cba"
//   console.log(item)
// }

// console.log(item)

let-const的暂时性死区

var foo = "foo";

// if (true) {
//   console.log(foo)
//  在声明变量之前,不能访问 这个实例中 {}代码块或者函数中 在let foo = "abc" 声明之前的区域是不能访问的foo的,这就叫做暂时性死区
//  暂时性死区就是个概念,了解就行
//   let foo = "abc"
// }

// 函数也是不能访问 --暂时性死区
function bar() {
  console.log(foo);

  let foo = "abc";
}

bar();

var name1 = "abc";
let name2 = "cba";
const name3 = "nba";

// 构建工具的基础上创建项目\开发项目 webpack/vite/rollup
// babel
// ES6 -> ES5

const info = { name: "why" };

info = { name: "kobe" };

如果new一个箭头函数的会怎么样

箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数。

new操作符的实现步骤如下:

  1. 创建一个对象
  2. 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)
  3. 指向构造函数中的代码,构造函数中的this指向该对象(也就是为这个对象添加属性和方法)
  4. 返回新的对象

所以,上面的第二、三步,箭头函数都是没有办法执行的。

new操作符的实现步骤如下:

<script>
      function Car(make, model, year) {
        this.make = make;
        this.model = model;
        this.year = year;
      }
      const car1 = new Car("Eagle", "Talon TSi", 1993);
      console.log(car1); //整个Car对象

      function Person() {
        this.name = "小明";
        this.fn = function () {
          console.log(`名字是${this.name}`);
        };
      }
      let person1 = new Person();
      console.log(person1.name); //小明
      person1.fn(); //名字是小明

      //      new 关键字会进行如下的操作:
      //     1.创建一个空的简单 JavaScript 对象(即 {});
      //     2.为步骤 1 新创建的对象添加属性 __proto__,将该属性链接至构造函数的原型对象;
      //     3.将步骤 1 新创建的对象作为 this 的上下文;
      //     4.如果该函数没有返回对象,则返回 this。

      // 人话 原理
      // 1.创建一个空对象
      let obj = new Object();
      // 2.链接到原型
      obj.__proto__ = Person.prototype;
      // 3.绑定this,执行一次构造函数 新创建的对象作为 this 的上下文;
      let result = Person.call(obj);
      // 返回一个对象
      if (typeof result === "object") {
        person1 = result;
      } else {
        person1 = obj;
      }
      console.log(obj.name); // 小明
    </script>
    </script>

箭头函数与普通函数的区别

(1)箭头函数比普通函数更加简洁

● 如果没有参数,就直接写一个空括号即可
● 如果只有一个参数,可以省去参数的括号
● 如果有多个参数,用逗号分割
● 如果函数体的返回值只有一句,可以省略大括号
● 如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字。最常见的就是调用一个函数:

(2)箭头函数没有自己的this

箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。

(3)箭头函数继承来的this指向永远不会改变

var id = 'GLOBAL';
var obj = {
  id: 'OBJ',
  a: function(){
    console.log(this.id);
  },
  b: () => {
    console.log(this.id);
  }
};
obj.a();    // 'OBJ'
obj.b();    // 'GLOBAL'
new obj.a()  // undefined
new obj.b()  // Uncaught TypeError: obj.b is not a constructor

对象obj的方法b是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象。需要注意,定义对象的大括号{}是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中。

(4)call()、apply()、bind()等方法不能改变箭头函数中this的指向

var id = 'Global';
let fun1 = () => {
    console.log(this.id)
};
fun1();                     // 'Global'
fun1.call({id: 'Obj'});     // 'Global'
fun1.apply({id: 'Obj'});    // 'Global'
fun1.bind({id: 'Obj'})();   // 'Global'

(5)箭头函数不能作为构造函数使用

构造函数在new的步骤在上面已经说过了,实际上第二步就是将函数中的this指向该对象。 但是由于箭头函数时没有自己的this的,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。

(6)箭头函数没有自己的arguments

箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。

(7)箭头函数没有prototype

(8)箭头函数不能用作Generator函数,不能使用yeild关键字

箭头函数的this指向哪⾥?

箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变。

 <script>
      var x = 11;
      var obb = {
        x: 222,
        y: {
          x: 333,
          obc: function f() {
            console.log(this);
            var x = 111;
            var obj = {
              x: 22,
              say: () => {
                console.log(this);
              },
            };
            obj.say();
          },
        },
      };
      obb.y.obc();
    //   原因箭头函数没有this,剪头函数的this是继承父执行上下文里面的this ,这里箭头函数的执行上下文是函数f(),所以它就继承了f()的this,
    </script>

扩展运算符的作用及使用场景

(1)对象扩展运算符

对象的扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。

let bar = { a: 1, b: 2 };
let baz = { ...bar }; // { a: 1, b: 2 }

上述方法实际上等价于:

let bar = { a: 1, b: 2 };
let baz = Object.assign({}, bar); // { a: 1, b: 2 }

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。(如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性)。

同样,如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。

    let bar = { c: 3, a: 1, b: 2 };
      let baz = { ...bar, ...{ a: 2, b: 4 } };
      console.log(baz); // {c: 3, a: 2, b: 4}

利用上述特性就可以很方便的修改对象的部分属性。在redux中的reducer函数规定必须是一个纯函数,reducer中的state对象要求不能直接修改,可以通过扩展运算符把修改路径的对象都复制一遍,然后产生一个新的对象返回。

需要注意:扩展运算符对对象实例的拷贝属于浅拷贝。

浅拷贝:只是拷贝数据的内存地址,而不是在内存中重新创建一个一模一样的对象(数组)

深拷贝:在内存中开辟一个新的存储空间,完完全全的拷贝一整个一模一样的对象(数组)

(2)数组扩展运算符

数组的扩展运算符可以将一个数组转为用逗号分隔的参数序列,且每次只能展开一层数组。

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

下面是数组的扩展运算符的应用:
● 将数组转换为参数序列

function add(x, y) {
  return x + y;
}
const numbers = [1, 2];
add(...numbers) // 3```

复制数组

```javascript
const arr1 = [1, 2];
const arr2 = [...arr1];

要记住:扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中,这里参数对象是个数组,数组里面的所有对象都是基础数据类型,将所有基础数据类型重新拷贝到新的数组中。
● 合并数组
如果想在数组内合并数组,可以这样:

const arr1 = ['two', 'three'];
const arr2 = ['one', ...arr1, 'four', 'five'];
// ["one", "two", "three", "four", "five"]

扩展运算符与解构赋值结合起来,用于生成数组

const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]

需要注意:如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

const [...rest, last] = [1, 2, 3, 4, 5];         // 报错
const [first, ...rest, last] = [1, 2, 3, 4, 5];  // 报错

将字符串转为真正的数组

[...'hello']    // [ "h", "e", "l", "l", "o" ]

● 任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组
比较常见的应用是可以将某些数据结构转为数组:

// arguments对象
function foo() {
  const args = [...arguments];
}

用于替换es5中的Array.prototype.slice.call(arguments)写法。
● 使用Math函数获取数组中特定的值

const numbers = [9, 4, 7, 1];
Math.min(...numbers); // 1
Math.max(...numbers); // 9

Proxy 可以实现什么功能?

在 Vue3.0 中通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。

Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。

let p = new Proxy(target, handler)

target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。

下面来通过 Proxy 来实现一个数据响应式:

let onWatch = (obj, setBind, getLogger) => {
  let handler = {
    get(target, property, receiver) {
      getLogger(target, property)
      return Reflect.get(target, property, receiver)
    },
    set(target, property, value, receiver) {
      setBind(value, property)
      return Reflect.set(target, property, value)
    }
  }
  return new Proxy(obj, handler)
}
let obj = { a: 1 }
let p = onWatch(
  obj,
  (v, property) => {
    console.log(`监听到属性${property}改变为${v}`)
  },
  (target, property) => {
    console.log(`'${property}' = ${target[property]}`)
  }
)
p.a = 2 // 监听到属性a改变
p.a // 'a' = 2

在上述代码中,通过自定义 set 和 get 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。

当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷就是浏览器的兼容性不好。

  var arr = [1, 2, 3, 4];
      function createArray(arr) {
        let handle = {
          get(target, index, receiver) {
            index = Number(index);
            if (index < 0) {
              index += target.length;
            }
            return Reflect.get(target, index, receiver);
          },
        };
        return new Proxy(arr, handle);
      }
      arr = createArray(arr);
      console.log(arr[-1]); //4

      //   var star = {
      //     name: "紫罗兰",
      //     age: 18,
      //     phoneNumber: "188888888888",
      //   };
      //   //   代理陷阱
      //   var proxy = new Proxy(star, {
      //     get: function (target, key, receiver) {
      //       if (key === "phoneNumber") {
      //         return "经纪人电话:18548451";
      //       } else {
      //         // return target[key];//两个方式 获取 第二种Reflect更常用
      //         return Reflect.get(target, key, receiver);
      //       }
      //     },
      //   });
      //   console.log(proxy.name); //紫罗兰
      //   console.log(proxy.age);//18
      //   console.log(proxy.phoneNumber);//经纪人电话:18548451

如何提取高度嵌套的对象里的指定属性?

const school = {
   classes: {
      stu: {
         name: 'Bob',
         age: 24,
      }
   }
}

像此处的 name 这个变量,嵌套了四层,此时如果仍然尝试老方法来提取它:

const { name } = school

显然是不奏效的,因为 school 这个对象本身是没有 name 这个属性的,name 位于 school 对象的“儿子的儿子”对象里面。要想把 name 提取出来,一种比较笨的方法是逐层解构:

const { classes } = school
const { stu } = classes
const { name } = stu
name // 'Bob'

但是还有一种更标准的做法,可以用一行代码来解决这个问题:

const { classes: { stu: { name } }} = school
       
console.log(name)  // 'Bob'

可以在解构出来的变量名右侧,通过冒号+{目标属性名}这种形式,进一步解构它,一直解构到拿到目标数据为止。

对 rest 参数的理解

扩展运算符被用在函数形参上时,它还可以把一个分离的参数序列整合成一个数组:

function mutiple(...args) {
  let result = 1;
  for (var val of args) {
    result *= val;
  }
  return result;
}
mutiple(1, 2, 3, 4) // 24

这里,传入 mutiple 的是四个分离的参数,但是如果在 mutiple 函数里尝试输出 args 的值,会发现它是一个数组:

function mutiple(...args) {
  console.log(args)
}
mutiple(1, 2, 3, 4) // [1, 2, 3, 4]

这就是 … rest运算符的又一层威力了,它可以把函数的多个入参收敛进一个数组里。这一点经常用于获取函数的多余参数,或者像上面这样处理函数参数个数不确定的情况。

ES6中模板语法与字符串处理

ES6 提出了“模板语法”的概念。在 ES6 以前,拼接字符串是很麻烦的事情:

var name = 'css'   
var career = 'coder' 
var hobby = ['coding', 'writing']
var finalString = 'my name is ' + name + ', I work as a ' + career + ', I love ' + hobby[0] + ' and ' + hobby[1]

仅仅几个变量,写了这么多加号,还要时刻小心里面的空格和标点符号有没有跟错地方。但是有了模板字符串,拼接难度直线下降:

var name = 'css'   
var career = 'coder' 
var hobby = ['coding', 'writing']
var finalString = `my name is ${name}, I work as a ${career} I love ${hobby[0]} and ${hobby[1]}`

字符串不仅更容易拼了,也更易读了,代码整体的质量都变高了。这就是模板字符串的第一个优势——允许用 的方式嵌入变量。但这还不是问题的关键,模板字符串的关键优势有两个:●在模板字符串中,空格、缩进、换行都会被保留●模板字符串完全支持“运算”式的表达式,可以在 {}的方式嵌入变量。但这还不是问题的关键,模板字符串的关键优势有两个: ● 在模板字符串中,空格、缩进、换行都会被保留 ● 模板字符串完全支持“运算”式的表达式,可以在 的方式嵌入变量。但这还不是问题的关键,模板字符串的关键优势有两个:在模板字符串中,空格、缩进、换行都会被保留模板字符串完全支持运算式的表达式,可以在{}里完成一些计算

基于第一点,可以在模板字符串里无障碍地直接写 html 代码:

let list = `
	<ul>
		<li>列表项1</li>
		<li>列表项2</li>
	</ul>
`;
console.log(list ); // 正确输出,不存在报错

基于第二点,可以把一些简单的计算和调用丢进 ${} 来做:

function add(a, b) {
  const finalString = `${a} + ${b} = ${a+b}`
  console.log(finalString)
}
add(1, 2) // 输出 '1 + 2 = 3'

除了模板语法外, ES6中还新增了一系列的字符串方法用于提升开发效率:
● 存在性判定:在过去,当判断一个字符/字符串是否在某字符串中时,只能用 indexOf > -1 来做。现在 ES6 提供了三个方法:includes、startsWith、endsWith,它们都会返回一个布尔值来告诉你是否存在。

  1. includes:判断字符串与子串的包含关系:
const son = 'haha' 
const father = 'xixi haha hehe'
father.includes(son) // true
  1. startsWith:判断字符串是否以某个/某串字符开头:
const father = 'xixi haha hehe'
father.startsWith('haha') // false
father.startsWith('xixi') // true
  1. endsWith:判断字符串是否以某个/某串字符结尾:
const father = 'xixi haha hehe'
  father.endsWith('hehe') // true
  1. 自动重复:可以使用 repeat 方法来使同一个字符串输出多次(被连续复制多次):
const sourceCode = 'repeat for 3 times;'
const repeated = sourceCode.repeat(3) 
console.log(repeated) // repeat for 3 times;repeat for 3 times;repeat for 3 times;

01_模板字符串的基本使用

// ES6之前拼接字符串和其他标识符
const name = "why";
const age = 18;
const height = 1.88;
// 拼接字符串和其他标识符
// console.log("my name is " + name + ", age is " + age + ", height is " + height)

// ES6提供模板字符串 `` 拼接字符串和其他标识符(变量,表达式等等) ${}
const message = `my name is ${name}, age is ${age}, height is ${height}`;
console.log(message);
// `` 拼接字符串和其他标识符(变量,表达式函数调用等等) ${}
const info = `age double is ${age * 2}`;
console.log(info);

function doubleAge() {
  return age * 2;
}
// `` 拼接字符串和其他标识符(变量,表达式函数调用等等) ${}
const info2 = `double age is ${doubleAge()}`;
console.log(info2);

展开语法进行的浅拷贝

在这里插入图片描述

 <script>
      const info = {
        name: "why",
        friend: { name: "kobe" },
      };
      // 展开运算符,其实做的就是一个浅拷贝

      const obj = { ...info, name: "coderwhy" };
      // console.log(obj)
      obj.friend.name = "james";
      console.log(obj.friend.name);

      console.log(info.friend.name);
    </script>

ES6中表示数值的方式

const num1 = 100; // 十进制

// b -> binary 英文缩写
const num2 = 0b100; // 二进制
// o -> octonary 八进位
const num3 = 0o100; // 八进制
// x -> hexadecimal
const num4 = 0x100; // 十六进制

console.log(num1, num2, num3, num4);

// 大的数值的连接符(ES2021 ES12)
const num = 10_000_000_000_000_000;
console.log(num);

Symbol的基本使用方式

// 1.ES6之前, 对象的属性名(key)
// var obj = {
// 这些对象里面的key,在底层其实就是字符串 如 "name":"why" ,"friend": { "name": "kobe" },...
//   name: "why",
//   friend: { name: "kobe" },
//   age: 18
// }

// obj['name'] = "james"
// 会覆盖之前的name
// console.log(obj)

// 2.ES6中Symbol的基本使用 用来生成一个独一无二的值
const s1 = Symbol();
const s2 = Symbol();

console.log(s1 === s2);

// ES2019(ES10)中, Symbol还有一个描述(description)
const s3 = Symbol("aaa");
console.log(s3.description);

// 3.Symbol值作为key的方式
// 3.1.在定义对象字面量时使用
const obj = {
  [s1]: "abc",
  [s2]: "cba",
};

// 3.2.新增属性
obj[s3] = "nba";

// 3.3.Object.defineProperty方式
const s4 = Symbol();
Object.defineProperty(obj, s4, {
  enumerable: true,
  configurable: true,
  writable: true,
  value: "mba",
});
// 获取key
console.log(obj[s1], obj[s2], obj[s3], obj[s4]);
// 注意: 不能通过.语法获取 ,这样他会找字符串的形式的key
// console.log(obj.s1)

// 4.使用Symbol作为key的属性名,在遍历/Object.keys等中是获取不到这些Symbol值
// 需要Object.getOwnPropertySymbols来获取所有Symbol的key
console.log(Object.keys(obj)); //[]空对象
console.log(Object.getOwnPropertyNames(obj)); //[]空对象
console.log(Object.getOwnPropertySymbols(obj)); //symbol有一个专门的方法getOwnPropertySymbols获取key值  [Symbol(),Symbol(),Symbol(aaa),Symbol()]
// 遍历key
const sKeys = Object.getOwnPropertySymbols(obj);
for (const sKey of sKeys) {
  console.log(obj[sKey]);
}

// 5.Symbol.for(key)可以创建相同的key
const sa = Symbol.for("aaa");
const sb = Symbol.for("aaa");
console.log(sa === sb);
// Symbol.keyFor(symbol)获取Symbol.for(key)的key值
const key = Symbol.keyFor(sa);
console.log(key);
const sc = Symbol.for(key);
console.log(sa === sc);

新增数据结构Set的使用

// 10, 20, 40, 333
// 1.创建Set结构
const set = new Set();
set.add(10);
set.add(20);
set.add(40);
set.add(333);

set.add(10);

// 2.添加对象时特别注意:
// 2.1添加两个对象 --不同的引用地址
set.add({});
set.add({});
// 2.2添加一个对象 --相同的引用地址
const obj = {};
set.add(obj);
set.add(obj);

// console.log(set)

// 3.对数组去重(去除重复的元素)
const arr = [33, 10, 26, 30, 33, 26];
// const newArr = []
// for (const item of arr) {
// indexOf()方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。,也就是只要返回的是-1才走if语句,只要不是-1就不走
//   if (newArr.indexOf(item) !== -1) {
//     newArr.push(item)
//   }
// }

const arrSet = new Set(arr);
// 这种情况下获取的是set的数据结构
// console.log(arrSet);
// 通过下面两种方式变成数组形式的数据结构
// 1.Array.from (迭代器--可迭代对象)
// const newArr = Array.from(arrSet)
// 2.... 展开运算符
// const newArr = [...arrSet]
// console.log(newArr)

// 4.size属性
console.log(arrSet.size);

// 5.Set的方法
// add
arrSet.add(100);
console.log(arrSet);

// delete 里面传入的是元素,不是索引--Set不能通过索引传入
arrSet.delete(33);
console.log(arrSet);

// has Set里面有没有传入的元素   返回值true或者false
console.log(arrSet.has(100));

// clear 清除所有元素
// arrSet.clear()
console.log(arrSet);

// 6.对Set进行遍历
// forEach
arrSet.forEach((item) => {
  console.log(item);
});
// for of
for (const item of arrSet) {
  console.log(item);
}

新增数据结构WeakSet的使用

在这里插入图片描述

const weakSet = new WeakSet();

// 1.区别一: WeakSet只能存放对象类型
// TypeError: Invalid value used in weak set
// weakSet.add(10)

// 强引用strong reference和弱引用weak reference的概念(看图)
// 强引用指向的不会被GC垃圾回收  如果只用弱引用的引用,也会被垃圾回收
// 2.区别二: 对对象是一个弱引用
let obj = {
  name: "why",
};

// weakSet.add(obj)

const set = new Set();
// 建立的是强引用
set.add(obj);

// 建立的是弱引用,如果没有其他引用指向,就会销毁
weakSet.add(obj);

// 3.WeakSet的应用场景
// 用来实现--不能通过非构造方法创建出来的对象调用running方法
// 不用set 是因为set是强引用 ,创建出来的对象也会被Set强引用一份,如果我们想要销毁对象 p =null 还需要在销毁Set里面的对象 personSet.delete(p)
const personSet = new WeakSet();
class Person {
  constructor() {
    personSet.add(this);
  }

  running() {
    if (!personSet.has(this)) {
      throw new Error("不能通过非构造方法创建出来的对象调用running方法");
    }
    console.log("running~", this);
  }
}

let p = new Person();
p.running();
p = null;

p.running.call({ name: "why" }); // 报错

新增数据结构Map的使用

// 1.以前  JavaScript中对象中是不能使用对象来作为key的
const obj1 = { name: "why" };
const obj2 = { name: "kobe" };

// const info = {
//   [obj1]: "aaa",
//   [obj2]: "bbb"
// }

// console.log(info)

// 2.Map就是允许我们对象类型来作为key的
// 构造方法的使用
const map = new Map();
// 对象类型来作为key的
map.set(obj1, "aaa");
map.set(obj2, "bbb");
// 也可以使用基本数据类型来作为key的
map.set(1, "ccc");
console.log(map);

const map2 = new Map([
  [obj1, "aaa"],
  [obj2, "bbb"],
  [2, "ddd"],
]);
console.log(map2);

// 3.常见的属性和方法
console.log(map2.size);

// set
map2.set("why", "eee");
console.log(map2);

// get(key)
console.log(map2.get("why"));

// has(key)
console.log(map2.has("why"));

// delete(key)
map2.delete("why");
console.log(map2);

// clear
// map2.clear()
// console.log(map2)

// 4.遍历map
map2.forEach((item, key) => {
  console.log(item, key);
});
// item[0]key 值  item[1]是value值 这里的item是个数组[item, key]
for (const item of map2) {
  console.log(item[0], item[1]);
}

for (const [key, value] of map2) {
  console.log(key, value);
}

新增数据结构WeakMap的使用

const obj = { name: "obj1" };
// 1.WeakMap和Map的区别二:
// 1.WeakMap是弱引用,map是强引用  强引用指向的不会被GC垃圾回收  如果只用弱引用的引用,也会被垃圾回收
const map = new Map();
map.set(obj, "aaa");

const weakMap = new WeakMap();
weakMap.set(obj, "aaa");

// 2.区别一: 不能使用基本数据类型,// WeakMap只能存放对象类型
// weakMap.set(1, "ccc")

// 3.常见方法
// get方法
console.log(weakMap.get(obj));

// has方法
console.log(weakMap.has(obj));

// delete方法
console.log(weakMap.delete(obj));
// WeakMap { <items unknown> }
console.log(weakMap);

响应式原理中的WeakMap使用

// 应用场景(vue3响应式原理)
const obj1 = {
  name: "why",
  age: 18,
};

function obj1NameFn1() {
  console.log("obj1NameFn1被执行");
}

function obj1NameFn2() {
  console.log("obj1NameFn2被执行");
}

function obj1AgeFn1() {
  console.log("obj1AgeFn1");
}

function obj1AgeFn2() {
  console.log("obj1AgeFn2");
}

const obj2 = {
  name: "kobe",
  height: 1.88,
  address: "广州市",
};

function obj2NameFn1() {
  console.log("obj1NameFn1被执行");
}

function obj2NameFn2() {
  console.log("obj1NameFn2被执行");
}

// 1.创建WeakMap
const weakMap = new WeakMap();

// 2.收集依赖结构
// 2.1.对obj1收集的数据结构
const obj1Map = new Map();
obj1Map.set("name", [obj1NameFn1, obj1NameFn2]);
obj1Map.set("age", [obj1AgeFn1, obj1AgeFn2]);
weakMap.set(obj1, obj1Map);

// 2.2.对obj2收集的数据结构
const obj2Map = new Map();
obj2Map.set("name", [obj2NameFn1, obj2NameFn2]);
weakMap.set(obj2, obj2Map);

// 3.如果obj1.name发生了改变
// Proxy/Object.defineProperty  --监听事件
obj1.name = "james";
const targetMap = weakMap.get(obj1);
const fns = targetMap.get("name");
fns.forEach((item) => item());

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值