ES6与高级
2021.9.11
class类的使用
类的本质就是function函数
在ES6中声明类用class关键字
- ES6中类没有变量提升,所以必须先定义类,才能实例化对象
- 类里面的共有属性和方法在内部调用,一定要加this使用
- constructor构造函数的this指向实例对象,方法里的this指向方法调用者(下面有案例)
创建类
//创建一个类的格式
class 类名 {
//constructor是构造函数,即使不写,也会自动创建
constructor(user,age){
//共用的属性放这里
this.user = user; //this必须加,指向的是创建的实例对象
this.age = age
}
//类中的方法
方法1(){} //实例对象访问
方法2(){}
// 静态属性,只能由构造函数访问(类名)
lang = "abc"
// 静态方法
static 方法3(){}
}
//调用类
var ldh = new 类名('李得还',18);
ldh.方法1();
类名.方法3();
继承类
class Father {} //父类
class Son extends Father{} //子类,extends为关键字,代表Son继承Father
super关键字调用父类的函数,构造函数
//父类
class Father {
say(){
return '父类'
}
}
//子类继承父类
class Son extends Father {
say(){
return super.say(); //调用父类的方法,将super当对象调用
}
}
//继承中,优先执行子类的方法,先看子类是否有该方法,如果没有则去执行父类中的该方法
super关键字调用父类的构造函数
//父类
class Father {
constructor(x,y){
this.x = x;
this.y = y;
}
sum(){
console.log(this.x + this.y);
}
}
//子类继承父类
class Son extends Father {
constructor(x,y){
super(x,y) //,参数传入父类构造函数,调用了父类中的构造函数,将super当方法用
}
say(){
return super.say(); //调用父类的方法,将super当对象调用
}
}
子类继承父类的方法,同时拥有自己的方法
//父类
class Father {
constructor(x,y){
this.x = x;
this.y = y;
}
sum(){
console.log(this.x + this.y);//必须加this.
}
}
//子类继承父类
class Son extends Father {
constructor(x,y){
//利用super 调用父类的构造函数
super(x,y); //super 必须先调用父类构造方法,在使用子类构造方法
//相当于 先有父类,在有子类
this.x = x;
this.y = y;
}
subtract(){
console.log(this.x - this.y)
}
}
constructor构造函数的this指向实例对象,方法里的this指向方法调用者
<button>点击</button>
<script>
class Father {
constructor(user) {
this.user = user;
this.btn = document.querySelector('button');
this.btn.onclick = this.sing;//绑定点击事件
}
sing(){
//当点击按钮后,这里的this指向的是btn按钮
console.log(this);
console.log(this.user); //btn按钮中没有该属性,所以会报错,无法输出abc
}
dance(){
//这里的this指向的是实例对象 ldh,因为是ldh调用了这个方法
console.log(this.user);
}
}
var ldh = new Father('abc');
ldh.dance()
</script>
原型对象
构造函数原型 prototype
构造函数通过分配的函数是所有对象所共享的
js规定,每一个构造函数都有一个prototype属性,指向另一个对象,这个prototype就是个对象,这个对象的所有属性和方法都会被构造函数拥有
可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例都可以共享这些方法,也节省了内存空间
function Star(uname,age){
this.uname = uname;
this.age = age;
//this.sing = function(){ console.log("哈哈哈") }
}
//将方法放到prototype对象上,节省空间
Star.prototype.sing = function(){ console.log("哈哈哈123") }
//初始化实例对象
let ldh = new Star('ddd', 18);
let adh = new Star('aaa', 18);
console.log(ldh.sing == adh.sing); //true,比较的是地址
console.log(Star.prototype); //构造函数对象
console.log(ldh.__proto__); //实例化对象
实例对象原型proto
实例化的对象都会有一个__proto__对象,对象原型对象__proto__指向构造函数的prototype原型对象
意义在于为对象的查找机制提供一个方向,实际开发中不使用这个属性(目前浏览器已不显示)
静态成员:在构造函数上添加的成员为静态成员,只能由构造函数本省来访问
实例成员:在构造函数内部创建的成员为实例成员,只能由实例化的对象来访问
js的成员查找机制
1.当访问一个对象属性(方法)时,先查找对象自身是否有该属性
2.如果没有就查找它的原型,也就是__proto__指向的prototype原型对象
constructor构造函数
对象原型(__proto__)和构造函数(prototype)原型对象里面都有个属性constructor属性,称为构造函数,因为它指向构造函数本身
constructor主要记录对象引用于哪个构造函数,可以让原型对象指向原来的构造函数
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
//将方法放到prototype对象上
Star.prototype.sing = function () {
console.log("哈哈哈123" + this.uname);
}
var ldh = new Star('ddd', 18);
console.log(Star.prototype.constructor); //输出的就是构造函数
console.log(ldh.__proto__.constructor); //输出的就是构造函数
有些情况下,需要手动将constructor属性指向原来的构造函数
//将方法放到prototype对象上
//对象的写法,相当于覆盖了原先的prototype对象
Star.prototype = {
//覆盖了,所以需要重新指向原来的构造函数
constructor: Star,
// 声明的静态方法
sing: function(){
console.log("唱歌");
},
movie: function(){
console.log("演电影");
}
}
构造函数,实例,原型对象三者之间的关系
Star构造函数 》 Star原型对象prototype 》 Star构造函数
Star构造函数 》 ldh对象实例 》 Star原型对象prototype 》 Star构造函数
原型链
成员查找机制,实现继承
实例对象__proto__指向
Star构造函数 》 ldh对象实例 》 Star原型对象prototype 》 Object原型对象 》 null
扩展内置方法
通过原型对象的原理,可以给内置对象增加自定义方法
// 给Array对象添加个求和方法
//给Array内置对象添加求和方法
Array.prototype.sum = function () {
let sum = 0;
for (let i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
}
// 调用添加的求和方法
let arr = [10, 20, 30];
console.log(arr.sum()); //60
内置的方法都存在原型对象上
继承
Call可以调用方法,也可以改变this的指向
function fn(){
console.log("哈哈哈");
console.log(this); //第一次window, 第二次o
}
fn.call(); // 调用方法
//声明对象
let o = {
name: 'andy'
}
//第一个参数就是要this指向的 对象
fn.call(o); //call可以改变this指向
借用构造函数继承父类型属性
// 父构造函数
function Father(uname,age){
this.uname = uname;
this.age = age;
}
// 子构造函数
function Son(uname,age){
// this 指向子构造函数的对象实例
Father.call(this,uname,age); //调用父构造函数,并将父构造函数的this指向Son,并将两个属性传递进去,此时Son已经拥有这两个属性了,也就是继承
}
var son = new Son('ldh',18);
console.log(son)
借用构造函数继承父类型方法(原型继承)
// 父构造函数
function Father(uname, age) {
this.uname = uname;
this.age = age;
}
//添加到原型上的方法
Father.prototype.money = function () {
console.log(10000);
}
// 子构造函数
function Son(uname, age) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age); //调用父构造函数,并将父构造函数的this指向Son,并将两个属性传递进去,此时Son已经拥有这两个属性了,也就是继承
}
// 继承父构造函数原型对象的方法
// 将子原型对象指向 父元素原型对象,这样可以继承到父原型对象的方法,但是会有问题,子会影响父
// Son.prototype = Father.prototype;
// 解决方法,
// 创建一个实例对象,赋值给Son原型对象,这样就继承了父原型对象的方法
// 因为是一个新的实例对象,所以不会影响父原型对象
Son.prototype = new Father();
// 如果利用对象的形式修改覆盖了原型对象,需要用constructor指回自己构造函数
Son.prototype.constructor = Son;
Son.prototype.exam = function () {
console.log("子元素考试");
}
var son = new Son('ldh', 18);
console.log(son)
console.log(Father.prototype);
console.log(Son.prototype.constructor);
预解析
代码先解析
var str; //声明变量
function add(){}; //声明函数
只声明,不会赋值
var sud = function(){}; //这种属于变量,只会预解析变量声明,后面的函数并不会解析
预解析优先级
//先解析的函数
console.log(a); //输出 函数体a
function a (){
console.log("aa")
}
var a = 1; //此时1值将函数a给覆盖了,所以此时a=1
console.log(a); //输出 变量a的值 1
let的预解析
let 定义的变量
1.也会预解析,但是必须先初始化赋值,才能使用(其它语言也是遵循此规则)
var则 可以先使用,在初始化
解构赋值
ES6中允许从数组中提取值,按对应位置,对变量赋值,对象也可以解构
数组解构
let arrs = [12, 52, 32]
let [a1, a2, a3] = arrs; //从数组按顺序提取数据存到变量
console.log(a1, a2, a3); //输出 12 52 32
// 按需取值
let [a,,b] = arrs; //用逗号占位即可
console.log(a,b); //a=12,b=32
多维数组
let arrs = ["A", "B", "C", ["A1", "A2", "A3"]];
let [,a,,[,b]] = arrs;
console.log(a,b); a 是"A" ,b是 "A2"
对象解构
let person = {name: "小丁", age: 18};
let { name, age } = person; //方式1
console.log(name, age);
let { name: mname } = person; //方式2
console.log(mname);
对象多层结构
let obj = {
uname:"哈哈哈",
dog: {
uname: "aaa"
}
}
let {dog:{uname}} = obj; 取对象中的对象中的uname
剩余参数与扩展运算符
ES6中的剩余参数和扩展运算符
剩余参数
区别:arguments是一个伪数组,剩余参数是一个真数组(Array),具有全部方法
但是在箭头函数中没有arguments,只能用剩余参数,就是3个点
...
剩余参数语法允许将一个不定数量的参数表示为一个数组
function sum(first, ...args) {
console.log(first); // 10
console.log(args); // [20, 30] 数组形式
}
sum(10, 20, 30);
剩余参数和解构配合使用
let students = ['wangwu', 'zhangsan', 'lisi'];
let [s1, ...s2] = students;
console.log(s1); // 'wangwu'
console.log(s2); // ['zhangsan', 'lisi']
扩展运算符
将数组转为用逗号分割的参数序列
let ary = [1, 2, 3];
...ary // 用逗号分割的结果是 1, 2, 3
console.log(...ary); // 1 2 3 这里逗号相当于console.log(1, 2, 3)
用于合并数组
// 方法一
let ary1 = [1, 2, 3];
let ary2 = [3, 4, 5];
let ary3 = [...ary1, ...ary2]; //合并
// 方法二
ary1.push(...ary2); //添加方式合并
将伪数组转真数组
let divs = document.getElementsByTagName('div');
divs = [...divs];
//利用数组方法转换
let arr = Array.from(divs);
Set 数据结构
它类似于数组,但是成员的值都是唯一的,没有重复的值
// 初始化实例对象
var s = new Set();
var s = new Set([2,3,4,5,4]); // 也可以接收数组作为参数来初始化
console.log(s); //{2, 3, 4, 5} 重复的4没有了
实例方法
add(value) 添加某个数据,返回Set结构本身
delete(value) 删除某个数据,返回布尔值,表示删除是否成功
has(value) 查询数据,存在返回true
clear() 清除结构中所有成员
遍历,与数组一样,拥有forEach方法
s.forEach(value => console.log(value))
创建对象
uname = "aaa";
age = 18;
let obj = {
uname, //在ES6中 如果属性名和变量名一样,则可以省略属性名
age
}
ES5新增方法
数组方法
迭代方法:forEach()、map()、filter()、some()、every(),find(),findIndex()
arr.forEach 迭代(遍历)数组
// arr.forEach(回调函数)
var arr = [1, 2, 3];
arr.forEach(function (value, index, array) {
console.log('每个数组元素 ' + value);
console.log('每个数组元素索引 ' + index);
cosole.log('数组本身 ' + array)
})
arr.filter 筛选数组
// arr.filter(回调函数)
var arr = [12, 77, 56, 1, 8, 20];
var newArr = arr.filter(function (value, index, array) {
return value >= 20;//找出大于20的数字
})
console.log(newArr); // 返回数组,包含所有满足条件的元素
arr.some 查找是否有满足条件的元素
var arr = [12, 35, 9];
var flag = arr.some(function (value, index) { return value == 35; }) //如果找到了则 立即停止迭代
console.log(flag); // true ,如果找到了则返回true
其它迭代方法
arr.find(); //遍历数组,查找满足条件的第一个成员并返回,不存在则返回undefined
arr.findIndex()//遍历数组,查找满足条件的第一个成员,并返回该索引值,不存在则返回-1
arr.some() // 查找满足条件的成员,如果找到 则立即停止遍历,然后返回true
arr.every() // 查找满足条件的元素,所有元素都满足 返回true,
map
// 遍历数组,给每个成员处理,然后得到处理后的新数组
let arr = [1, 3, 2, 4, 5, 6, 7, 8]
let newArr= arr.map(function (value, index) {
return value * 2; //每个元素乘2
})
console.log(newArr); // [2, 6, 4, 8, 10, 12, 14, 16]
reduce
//reduce()遍历数组,将结果汇总为单个返回值
arr.reduce(回调函数,[初始值]) //未提供初值则会跳过第0次循环,从1开始
//回调函数有4个参数
accumulator(累计器acc) 第一次遍历 该值是初始值
currentValue(当前值cur)
[index](当前值索引)
[array](源数组)
//能实现的功能
取字符出现次数,数组去重,多维转1维
Array.from() 遍历数组,可将伪数组转换真数组
let arr = {
"0": 1,
"1": 2,
"length": 2
}
let newarr = Array.from(arr, item => tiem)
方法
includes()方法,查找数组是否包含某给值,返回布尔值
var arr = ["abc", "dd", "ee"];
arr.includes("dd"); // true
对象方法
Object.values() 获取所以属性值
Object.keys() 用于获取对象自身的所有属性名
var obj = {
id: 1,
pname: '小米',
price: 1999
}
var arr = Object.keys(obj); //得到属性名的数组
console.log(arr); //["id", "pname", "price"]
Object.defineProperties 定义新属性或者修改原有属性
Object.defineProperties(obj对象,prop属性名,descriptor对象形式参数)
// 定义新属性 或修改原有的属性
Object.defineProperties(obj, 'num', {
value: 9.9, //定义或修改原属性值
writable: false, //值是否可重写true 默认false
enumerable: false, //属性是否可被枚举(遍历) true 默认false
configurable: false //属性是否可被删除或修改特性 true 默认false
})
函数
所有函数都是Function的实例(对象)
判断的对象 instanceof 用于比较的对象
定义函数的方式
// 带名字的函数
function fn(){};
// 匿名函数
let fn = function(){};
//实例化函数写法
let fn = new Function('参数1','参数2','函数体');
// 例如
let fun = new Function('a','b','console.log(a + b)');
fun(1,2); //调用
函数形参默认值
// 函数的参数的默认值,在没有传参的时候,会使用默认值
function getInfo(a=0,b=2){
return a + b;
}
getInfo(undefined,2); //第一个参数留空要写 undefined填充,
函数内this指向
调用方式 | this指向 |
---|---|
普通函数调用 | window |
构造函数调用 | 实例化的对象,原型对象里的方法也指向实例对象 |
对象方法调用 | 该方法所属对象 |
事件绑定方法 | 绑定事件的对象 |
定时器函数 | window |
立即执行函数 | window |
改变函数内this指向
三种方法,call() apply() bind()
Call改变this的指向,常用语继承
function fn(){
console.log("哈哈哈");
console.log(this); //第一次window, 第二次o
}
fn.call(); // 调用方法
//声明对象
let o = {
name: 'andy'
}
//第一个参数就是要this指向的 对象
fn.call(o); //call可以改变this指向
apply改变this的指向
// 用法 fun.apply(thisArg指向的对象,[argsArray]伪数组)
var o = { name: 'andy' };
function fn (arr){ console.log(this);console.log(arr) };
fn.apply(o,['pink']); //伪数组
//可用于Math.Max()传递数组来取最大值
//自己使用需要arguments来获取数组每个元素
bind 改变this的指向
但是不调用函数,返回改变this之后的新函数
可用于回调函数,方便指向this
var o = { name: 'andy' };
function fn (arr){ console.log(this);console.log(arr) };
var f = fn.bind(o); //不会调用,返回改变this之后的新函数
f();
改变函数内this指向,但是不会立即执行
var btn = document.querySelector('button');
btn.onclick = function(){
this.disabled = true; 点击后禁止按钮
// 启动临时定时器,3秒后执行
setTimeout(function(){
this.disabled = false;//3秒后解开按钮
}.bind(btn),3000) // bind改变函数内的this指向
}
箭头函数
声明函数
//正常的写法
function sum(s1, s2) { return s1 + s2 };
//箭头函数写法
const sum = (s1, s2) => { return s1 + s2 };
//简写,当函数体只有一句代码,且执行结果就是返回值,可以省略大括号,和return
const sum = (s1, s2) => s1 + s2;
function fn(a) { return a; }
//如果形参只有一个,可以省略小括号
const fn = a => a;
fn(12);
箭头函数不绑定this关键字,箭头函数中的this指向 函数定义位置的上级作用域this
也不能用call修改this
//作用域this
let obj = {
age: 18,
say: () => {
alert(this.age); // 输出undefined
// 因为上级作用域是window,window下没有age变量
},
hel: function(){
console.log(this) //输出obj,调用者
}
}
obj.say();
obj.hel();
箭头函数中的this指向位置
function get() {
say = () => {
console.log(this); //输出 window对象
// 上级作用域是get(),get()的this是window
}
say();
}
get();
严格模式
代表在严格的条件下运行JS代码
消除了js语法的一些不合理,不严谨之处,和不安全之处,提高编译器效率
禁用了一些未来可能出现的语法关键字比如,class,enum,export,extends,import,super 不能用做变量名
变量必须声明 才能使用,不能使用delete删除已经定义的变量
开启严格模式
<script>
'use strict'
//后面的代码都将执行严格模式
</script>
开启严格模式-给函数开启
function(){
'use strict'
//这里面的代码将执行严格模式
}
严格指向的问题
1.以前在全局作用域函数中的 this 指向 window 对象。
2.严格模式下全局作用域中函数中的 this 是 undefined。
3.以前构造函数时不加 new也可以 调用,当普通函数,this 指向全局对象
4.严格模式下,如果 构造函数不加new调用, this 指向的是undefined 如果给他赋值则 会报错
5.new 实例化的构造函数指向创建的对象实例。
6.定时器 this 还是指向 window 。
7.事件、对象还是指向调用者。
8.不允许在if for 里面声明函数
高阶函数
高阶函数是 对其它函数进行操作的函数
参数形参是函数的就是高阶函数,返回值是函数的也属于高阶函数
function(fn){ //形参里有函数,高阶函数的特征
fn();
}
function fn(){
console.log("123");
}
闭包
衍伸了变量的作用范围,可以在外面访问内部作用域的局部变量
Scope 里面会有两个参数(global 全局作用域、local 局部作用域)。浏览器的Scope里面多一个Closure参数,这就说明产生了闭包
function fn(){
var num = 10;
return function(){ console.log(num) };
}
var f = fn();
f() 执行了局部作用域的函数 和变量
// 闭包的简单例子,2秒后打印所以的li标签
var li = document.querySelectorAll("li");
for (var i = 0; i < li.length; i++) {
// i是变动的,等到事件执行的时候i已经变成4了,所以需要闭包
// 创建一个立即执行函数,将i传递进去,此时a变量会一直保存到事件执行完毕
(function(a){
setTimeout(function () {
console.log(li[a].innerHTML);
}, 2000)
})(i)
}
递归
函数调用自身来达到循环执行的目的
var i = 0;
function prote(){
i++;
console.log(i); // 会输出6次,然后停止
if(i>=6) return;
prote()
}
prote();
浅拷贝深拷贝
浅拷贝=只拷贝第一层数据,深层次拷贝的是引用地址,深拷贝=复制数据到新的内存空间地址
Object.assign() 拷贝属于浅拷贝,不能拷贝深层次的对象数据。如果属性名相同会覆盖
Object.assign(存放的对象,要拷贝的对象);
// 深拷贝对象(保存的变量,待拷贝对象)
function lstrcpy(paste, oringe) {
for (const k in oringe) {
// 必须先判断数组,因为数组也是对象
if (oringe[k] instanceof Array) {
paste[k] = []; //设置成空数组
lstrcpy(paste[k], oringe[k])
} else if (oringe[k] instanceof Object) {
paste[k] = {}; // 设置成空对象
lstrcpy(paste[k], oringe[k])
} else {
paste[k] = oringe[k];
}
}
}
var obj = JSON.parse(JSON.stringify(待拷贝对象)); //转换方式深拷贝
异常捕获处理
用于处理运行中可能会发生错误的代码,进行捕获处理后 程序就不会中断 就能继续往下执行
try…catch异常捕获
//接管代码错误处理
try {
// 用try包裹可能发生错误的代码
p = document.querySelectorAll("p");
p.style.color = "red";
} catch (error) {
// 用catch写当发生错误时要如何处理
console.log(error, error.message); // 输出错误信息
}
// 即使前面代码抛出异常了,由于处理了异常,所以程序还能正常执行
document.querySelector("p").style.backgroundColor = "blue"
throw抛出错误,Error错误对象
function getinfo(x, y) {
if (x == undefined || y == undefined) {
throw new Error("getinfo参数不能为空"); //抛出错误
}
return x + y;
}
// throw抛出异常后,程序将会停止执行
//new Error("错误提示信息"); 错误对象
禁止调试代码
setInterval(function () {
check()
}, 4000);
var check = function () {
function doCheck(a) {
if (("" + a / a)["length"] !== 1 || a % 20 === 0) {
(function () { }
["constructor"]("debugger")())
} else {
(function () { }
["constructor"]("debugger")())
}
doCheck(++a)
}
try {
doCheck(0)
} catch (err) { }
};
check();