ES6知识
字面量增强的写法的三种写法
ES6中对 对象字面量 进行了增强,称之为 Enhanced object literals(增强对象字面量)。
字面量的增强主要包括下面几部分:
- 属性的简写:Property Shorthand
- 方法的简写:Method Shorthand
- 计算属性名: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操作符的实现步骤如下:
- 创建一个对象
- 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)
- 指向构造函数中的代码,构造函数中的this指向该对象(也就是为这个对象添加属性和方法)
- 返回新的对象
所以,上面的第二、三步,箭头函数都是没有办法执行的。
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,它们都会返回一个布尔值来告诉你是否存在。
- includes:判断字符串与子串的包含关系:
const son = 'haha'
const father = 'xixi haha hehe'
father.includes(son) // true
- startsWith:判断字符串是否以某个/某串字符开头:
const father = 'xixi haha hehe'
father.startsWith('haha') // false
father.startsWith('xixi') // true
- endsWith:判断字符串是否以某个/某串字符结尾:
const father = 'xixi haha hehe'
father.endsWith('hehe') // true
- 自动重复:可以使用 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());