前言
ES6
拓展运算符
能将数组转换为逗号分隔的 [参数序列]
与rest参数有所不同
扩展运算符放在了函数调用的实参立里面
而rest放在了函数声明的形参位置
1.应用
1)数组的合并
2)数组的克隆(浅拷贝)
3)将伪数组转为真正的数组
rest参数
Es6引入rest参数 用于获取函数的实参,用来代替arguments
Es5的获取方式不是返回一个数组,而是一个object对象
利用rest获取的是一个数组,可以用数组的各种方法
注意:rest参数必须放在参数最后
Symbol
Es6引入了一种新的原始数据类型Symbol,表示独一无二的值,它是javascript语言的第七种数据类型,是一种类似于字符串的数据类型
1.Symbol()和Symbol.for()
Symbol 函数的参数只是表示对当前Symbol值的描述,因此相同参数的Symbol函数的返回值是不相等的,symbol.for访问的是一个全局的Symbol表,如果有了就访问对应对象,没有就重新创建
2.特点
● symbol的值是唯一的
● Symbol值不能与 其他数据类型进行运算
● Symbol定义的对象属性不能使用for…in…循环遍历,但是可以使用Reflect.ownKeys来获取对象所有的键名
3.对象添加Symbol类型的属性
//向对象中添加方法 up down
let game = {
name:'俄罗斯方块',
up: function(){},
down: function(){}
};
// 声明一个对象
let methods = {
up: Symbol(),
down: Symbol()
};
game[methods.up] = function(){
console.log("我可以改变形状");
}
game[methods.down] = function(){
console.log("我可以快速下降!!");
}
game[methods.up]();
game[methods.down]();
let youxi = {
name:"狼人杀",
[Symbol.for('say')]: function(){
console.log("我可以发言")
},
[Symbol.for('zibao')]: function(){
console.log('我可以自爆');
}
}
console.log(youxi);
//Symbol.for('say')产生的标识是唯一的
//所以可以调用定义在youxi里面的[Symbol.for('say')]方法
youxi[Symbol.for('say')]();
youxi[Symbol.for('zibao')]();
let youxi1 = {
name:"狼人杀",
[Symbol('say')]: function(){
console.log("我可以发言")
},
[Symbol('zibao')]: function(){
console.log('我可以自爆');
}
}
youxi[Reflect.ownKeys(youxi)[1]]();
youxi[Reflect.ownKeys(youxi)[2]]();
//静态方法 Reflect.ownKeys() 返回一个由目标对象自身的属性键组成的数组。
//这种情况无法直接调用,因为每次 Symbol("xxx") 都是不一样的
//因为Symbol('say')生成的标识不是唯一的,所以定义的在youxi1里面的[Symbol('say')]是无法调用的
//除非把Symbol('say')放块外面定义,并用变量保存
console.log(Symbol('say')===Symbol('say')); //false
// youxi1[Symbol('say')](); //报错
// youxi1[Symbol('zibao')](); //报错
4.Symbol 的内置值![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/3c6eeef86b95731dc2527b6b9b21be8c.png)
1)hasInstance
2)isConcatSpreadable
Iterator 迭代器
1.作用
Iterator 的作用有三个:
一是为各种数据结构,提供一个统一的、简便的访问接口;
二是使得数据结构的成员能够按某种次序排列;
三是 ES6 创造了一种新的遍历命令for…of循环,Iterator 接口主要供for…of消费。
2.遍历过程
1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
生成器
1.特点
Generator 函数 是一个普通函数,但是有两个特征
1.function 关键字与函数名之间有一个星号
函数体内部使用yield表达式,定义不同的内部状态
理解 Generator 调用后该函数并不执行,返回的也不是函数运行的结果,而是一个指向内部状态的指针对象,必须调用next()方法才可执行,yield表达式是暂停执行的标记(即每次执行都会执行yield之间的语句,遇到yield会暂停,next方法会恢复执行)
上面代码中 yield后面的表达式1+2 不会立即求值,只会在next方法将指针移到这一句时才会求值
如果在控制台打印 h.next() 不仅会将yield之间的表达式执行,还会打印出value值和done
return 和yield的区别
相似之处:都能返回紧跟在语句后面的那个表达式的值
区别:每次遇到yield 函数暂停执行,下一次再从该位置继续向前执行,而return 语句不具备位置记忆的功能,一个函数里面,只能执行一次(或者说一个)return 语句,但是可以执行多次(或者说多个)yield表达式。正常函数只能返回一个值,因为只能执行一次return;Generator函数可以返回一系列的值,因为可以有任意多个yield
注意:yield表达式如果在另一个表达式中,必放在圆括号里
forEach函数不能使用yield,因为forEach函数是一个普通函数,可以使用for循环和for of
2.生成器函数的参数传递
1)next参数
next方法可以传递实参
:第二个next()传入的实参将作为第一个yield语句的返回值
集合
1.Set
类似于数组但是成员值唯一,该集合实现了Iterator接口,所以可以是使用拓展运算符和for…of 进行遍历
1)集合的属性和方法
(1)size 返回元素的个数
(2)add 增加一个新元素,返回当前集合
(3)delete 删除元素,返回布尔
(4)has()判断元素是否在集合中,返回布尔
(5)clear()清空所有成员,没有返回值
2)Object 结构与Set结构的写法不同
3)将Set转换为数组(将数组去重的方法之一)
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
数组去重
function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]) // [1, 2, 3]
let arr=[1,2,3,4,5,4,3,2,1];
//new Set(arr)可将其转为类数组,而且里面元素是不重复的,
//...是扩展运算符 将类数组转为数组
let result=[...newSet(arr)];
console.log(result);
4)4个遍历方法
(1)keys() 返回键名的遍历器
(2)values() 返回键值
(3)entries()返回键值对
(4)forEach()使用回调函数遍历每个成员
注意:由于Set方法没有键名只有键值(或者是键名和键值是同一个值),所以keys等于values
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
5)实践
(1)求交集
filter()方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
item指调用者的所有的元素,方法中的是条件,filter会返回一个新数组,数组中的元素均满足该条件
let arr2 = [4, 5, 6, 5, 6];
let s2 = new Set(arr2);
let result2 = [...new Set(arr2)].filter((item) => s2.has(item));
console.log(result2); //[4,5,6]
(2)求并集
let union=[...new Set([...arr,...arr2])]
(3)求差集
// arr2的差集(arr中有arr2没有的)
let diff = [...new Set(arr)].filter((item) => !new Set(arr2).has(item));
console.log(diff);
2.Map
类似于对象,也是键值对的集合,但是键的范围不限于字符串,各种类型的值(包括对象)都可以当做键,Map也实现了iterator接口,所以可以使用拓展运算符和for…of进行遍历
1)属性和方法
(1)size 返回MAP元素的个数
(2)set 增加一个元素,返回当前的map
(3) get 返回键名对象的键值
(4)has 检查Map中是否包含某个元素,返回Boolean值
(5)clear 清空集合 返回Undefined
Class
1.简介
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰
2.使用
// 当使用new加类名的时候,就会自动执行实例对象的constructor方法
class Phone {
// 构造方法,名字固定
constructor(brand, price) {
this.brand = brand;
this.price = price;
}
// 方法必须使用该语法,不能使用ES5的对象完整形式
call() {
console.log("我可以打电话");
}
}
let onePlus = new Phone("1+", 1999);
onePlus.call();
3.静态成员
注意:对于static 标注的属性和方法。它属于类,并不属于创建的实例对象
如果静态方法包含this关键字,这个this指的是类,而不是实例
所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
4.类的继承
父类的静态方法可以被子类继承,静态方法也可以从super对象上调用
注意:父类中所有的属性和方法,都会被子类继承,除了私有的属性和方法(除非使用特殊方法)
class Phone {
constructor(brand, price) {
this.brand = brand;
this.price = price;
}
call() {
console.log("打电话");
}
}
class smartPhone extends Phone {
//构造方法
constructor(brand, price, color, size) {
super(brand, price);
this.color = color;
this.size = size;
}
photo() {
console.log("拍照");
}
playGame() {
console.log("玩游戏");
}
}
const xiaomi = new smartPhone("小米", 799, "黑色", "4.7英尺");
console.log(xiaomi);
xiaomi.call();
1)私有属性和私有方法的继承
子类无法继承父类的私有属性,或者说私有属性只能定义它的class里面使用
如果父类定义了私有属性的读写方法,子类就可以通过这些方法,读写私有属性
class Foo {
#p = 1;
getP() {
return this.#p;
}
}
class Bar extends Foo {
constructor() {
super();
console.log(this.getP()); // 1
}
}
5.get和set
注意:与java有所不同
class Phone {
a = 1;
get price() {
console.log("价格属性被读取了");
return this.a;
}
set price(value) {
console.log("setter:", value);
this.a = value * 2;
}
}
let s = new Phone();
// getter 是取值函数,setter是存值函数
s.price = 2000; //存值时调用setter方法
console.log(s.price); //取值时调用getter方法
6.class的私有属性
在属性名之前用# 表示
只能在类的内部使用,如果在外部使用会报错
由于#是属性名的一部分,因此#x和x是两个不同的属性
这种方法不仅可以写私有属性,还可以写私有方法
另外私有属性也可以设置getter和setter方法
class Counter {
#xValue = 0;
constructor() {
console.log(this.#x);
}
get #x() { return this.#xValue; }
set #x(value) {
this.#xValue = value;
}
}
Module
模块功能主要由两个命令构成 :export 和import
export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
1.export 命令
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量
范例:
//proifile.js文件
//法一
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
//法二(推荐)
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
// main.js文件
import { firstName, lastName, year } from './profile.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
export 命令还可以输出函数和类
export function multiply(x, y) {
return x * y;
};
通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名
//代码使用as关键字,重命名了函数v1和v2的对外接口。
//重命名后,v2可以用不同的名字输出两次。
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
需要注意的是:export 命令规定的是对外接口,必须与模块内部的变量建立一一对应的关系
//错误写法
// 报错
export 1;
// 报错
var m = 1;
export m;
上面两种写法都会报错,因为没有提供对外的接口。第一种写法直接输出 1,第二种写法通过变量m,还是直接输出 1,1只是一个值,不是接口。正确的写法是下面这样。
// 写法一
export var m = 1;
// 写法二
var m = 1;
export {m};
// 写法三
var n = 1;
export {n as m};
// 报错
function f() {}
export f;
// 正确
export function f() {};
// 正确
function f() {}
export {f};
2.import
import { lastName as surname } from './profile.js';
注意:import命令输入的变量都是只读,不能重新赋值,因为本质是输入接口
高级JS
原型与原型链
1.原型(prototype)
1)函数的protype属性
每个函数都有一个protype属性,它默认指向一个Object空对象(即称为原型对象,理解:没有我们的属性 )
原型对象中有一个属性constructor ,它指向函数对象
2)给原型对象添加属性(一般是方法)
作用:函数的所有实例对象自动拥有原型中的属性(方法)
原型上的方法是给实例对象用的
如果函数名叫Type,有一个属性叫prototype,这个属性指向Type的原型对象,原型对象里有个属性constructor,constructor又指向Type
即 我(Type)爷爷(prototype)的孙子(constructor)是我(Type)
2.显示原型和隐式原型
1)每个函数function都有一个prototype ,即显式原型
2)每个实例对象都有一个 proto 属性,可称为隐式原型
3)对象的隐式原型的值为其对应构造函数的显式原型的值
4)内存结构(图)
5)总结
函数的prototype属性:在定义函数时自动增加的,默认值是一个空Object对象
对象的__proto__属性:创建对象时自动添加的,默认值为构造函数的prototype属性值
3.原型链
- 原型链(图解)
test1不在Fn上,实在this上,这里的this是fn,所以挂在了fn上,Fn上根本就没有test1
访问一个对象的属性时,先在自身属性中查找,找到返回
如果没有,再沿着__proto__这条链向上查找,找到返回
如果最终没找到,返回Undefined
*别名:隐式原型链
*作用:查找对象的属性(方法)
2)构造函数/原型/实体对象的关系(图解)
3)构造函数/原型/实体对象的关系2(图解)
理解一:总的来说,原型中有一个总共的父原型,创建的函数对应的是一个个的子原型,可以通过原型的原型来返回到最开始的父原型中
理解二:我们创建的函数对象都是object的实例,所以都有一个__proto__属性指向object的原型对象,而object的原型对象里面有一些固定方法, 所以后面的实例对象都可以通过这个原型链访问到那些方法
4.原型链的属性问题
1)读取对象的属性值时,会自动到原型链中查找
2)设置对象的属性值时,不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值
3)方法一般定义在原型中,属性一般通过构造函数定义在对象本身上
5.探索instanceof
1.instanceof 是如何判断的
表达式 A instanceof B
如果B函数的显式原型对象在A对象的原型链上,返回true
2.Function 是通过new 自己产生的实例
所有函数的实例对象都是Object的实例,Object自身除外
Object作为实例对象时,会指向Function的原型对象,再指向Object自身原型对象
6.面试题
打印出来是 1 Undefined 2 3
因为A改写了原型prototype,在堆内存重新开辟了空间,而b还是指向原来的内存地址
执行上下文与上下文栈
1.变量提升和函数提升
1.变量声明提升
通过var定义(声明)的变量,在定义语句之前就可以访问到
值;undefined
2.函数声明提升
通过function声明的函数,在之前就可以调用、
值:函数定义(对象)
3.问题:变量提升和函数提升是如何产生的?
2.执行上下文
1.代码分类(位置)
*全局代码
*函数(局部)代码
2.全局执行上下文
● 在执行全局代码前将window确定为全局执行上下文
● 对全局数据进行预处理
*var 定义的全局变量=>undefined 添加为window属性
*function 声明的全局函数=>赋值(fun),添加为window的方法
* this=>赋值(window)
● 开始执行全局代码
3.函数执行上下文
● 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象(虚拟的,存在于栈中)
● 对局部数据进行预处理
*形参变量=>赋值(实参)=>添加为执行上下文的属性
*arguments=>赋值(实参列表),添加为执行上下文的属性
*var 定义的局部变量=>undefined 添加为执行上下文的属性
*function声明的函数=>赋值(fun)添加为执行上下文的方法
*this=> 赋值 (调用函数的对象)
● 开始执行函数体代码
3.执行上下文栈
1)在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2)在全局执行上下文(window)确定后,将其添加到栈中(压栈)
3)在函数执行上下文创建后,将栈顶的对象移除(出栈)
4)在当前函数执行完后,将栈顶的对象移除(出栈)
5)当所有的代码执行完后,栈中只剩下window
后进先出
总结
这周学习了Es6和js高级,深度逐渐增加,不过还好之前学超哥的课有所了解现在的高级js,不至于听不懂,下周计划:将这两周学习的知识掌握