淘先锋技术网

首页 1 2 3 4 5 6 7

ES6

var const let

区别

varconstlet
变量提升
块作用域 { }
同一作用域下,声明同名变量可以不可以不可以
声明的值、类型、变量能否改变可以基础数据类型不能改变,引用数据类型可以修改其属性可以
暂时性死区域
window挂载

变量提升:

var 有变量提升

var a = 1;
console.log(a); // 1

console.log(a); // undefined
var a = 1; 
//上面代码预解析
var a;
console.log(a); // undefined
a = 1;

const与let 没有变量提升

const b = 1;
console.log(b); // 1

let b = 1;
console.log(b); // 1
console.log(b) // 报错
const b = 1;

console.log(b) // 报错
let b = 1;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b0Oud2fE-1657280554681)(C:\Users\Timi\AppData\Roaming\Typora\typora-user-images\image-20220509204906927.png)]

块作用域 { }

ES5只有全局作用域、函数作用域。ES6有了块级作用域,块级就是{ }, 花括号内就是块级作用域的范围

声明的值、类型、变量能否改变

① 值类型:简单数据类型/基本数据类型,在存储时变量中存储的是值本身,因此叫做值类型

② 引用类型:复杂数据类型,在存储时变量中存储的仅仅是地址(引用)因此叫做引用数据类型

简单类型的内存分配

① 值类型(简单数据类型):string , number , boolean , undefined , null

② 值类型变量的数据直接存放在变量(栈空间)中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wHPs2caL-1657280554683)(C:\Users\Timi\AppData\Roaming\Typora\typora-user-images\image-20220410164131254.png)]

复杂类型的内存分配

① 引用类型(复杂数据类型):通过 new 关键字创建的对象(系统对象、自定义对象)、如Object、Array、Date等

② 引用类型变量(栈空间)里面存放的是地址,真正的对象实例存放在堆空间中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6efgJ5VW-1657280554683)(C:\Users\Timi\AppData\Roaming\Typora\typora-user-images\image-20220410164654129.png)]

暂时性死区域:

var a = 9;
let b = 9;
console.log(a, b)//9,9
{
   console.log(a)//报错:a is not defined;
   console, log(b)//报错:b is not defined     
 //这就造成了暂时性死区了(1.封闭的环境2.不存在变量提升)
   var a = 8
   let a = 8;
   let b = 8;
   console.log(a)//8;
   console.log(b)//8
}

window挂载

①var可以挂载

var a = 1;
console.log(window.a); // 1

②const不可以挂载

const a = 1;
console.log(a); // 1
console.log(window.a); // undefined

③let不可以挂载

let a = 1;
console.log(a); // 1
console.log(window.a); // undefined

const

① const定义的变量是不可以被改变的,但是这个仅限于描述定义的基本数据类型(number、string、布尔型)像(function、array、object)这样的其实是可以被改变的,(基本数据类型可直接赋值在变量上,像function、array、object这样的只是把一个地址赋值给了变量)

**注意:**const定义的变量必须要在定义的时候同时赋值;

死区:let 是定义变量的关键字,当解析器进入一个块级作用域,发现let关键字,变量只是先完成声明,并没有到初始化那一步。此时如果在此作用域提前访问,则报错xx is not defined,这就是暂时性死区的由来。等到解析到有let那一行的时候,才会进入初始化阶段。如果let的那一行是赋值操作,则初始化和赋值同时进行

解构赋值

数组解构赋值

let  [a,b,c,d ]  =  [1,2,3,4];
consloe.log(a);

对象解构赋值

let {name,age} = {
    name:'kgc',
    age:18
}
console.log(name)

字符串的扩展

模板字符串

// 以前
var str = 'There are <p>' + count + '</p>'
// 现在
var newStr = `There are <p>${count}</p>`

includes() 查找是否包括指定字符

includes() 方法用于判断字符串是否包含指定的子字符串。

如果找到匹配的字符串则返回 true,否则返回 false。

function fn() {
  let str = "Hello world";
  let n = str.includes("world");
  console.log(n); // true
}
fn();

repeat() 重复次数

repeat() 方法字符串复制指定次数。

let str = "Runoob";
let newStr = str.repeat(2);
console.log(newStr); // RunoobRunoob

padStart() 往前添加

返回新的字符串,表示用参数字符串从头部(左侧)补全原字符串。

如果没有指定第二个参数,默认用空格填充

如果指定的长度小于或者等于原字符串的长度,则返回原字符串:

padEnd() 往后添加

返回新的字符串,表示用参数字符串从尾部(右侧)补全原字符串。

如果原字符串加上补全字符串长度大于指定长度,则截去超出位数的补全字符串:

常用于补全位数

数组扩展

forEach() 循环

forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。

注意: forEach() 对于空数组是不会执行回调函数的。

<div id="box"></div>
<script>
        let arr = [1, 2, 3, 4, 5, 6]
        arr.forEach(function (item, index) {
            console.log(item, index);
            box.innerHTML += item;
        }, box);
</script>

map() 循环为同一项执行同一操作

map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。

map() 方法按照原始数组元素顺序依次处理元素。

注意: map() 不会对空数组进行检测。

注意: map() 不会改变原始数组。

let data = [
    {name:'刘哲',age:20,sex:'女'},
    {name:'许博恩',age:18,sex:'女'}
]
let arr = data.map(function(item,index){
    let obj = {};
    obj.objName = item.name;
    obj.age = item.age+10;
    obj.sex = item.sex;
    obj.sex = '男';
    return obj
})
console.log(arr);
console.log(data);

filter() 过滤

filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。

注意: filter() 不会对空数组进行检测。

注意: filter() 不会改变原始数组。

let arr = [
    {title:'aaa',ready:100,hot:true},
    {title:'bbb',ready:100,hot:false},
    {title:'ccc',ready:100,hot:false},
    {title:'ddd',ready:100,hot:true}
];
let newArr = arr.filter(function(item,index){
            return item.hot //返回 hot为true的
            // return item.hot===false; //返回 hot为false的
        })
console.log(newArr);

some() 查找

some() 方法用来检测数组中是否存在符合指定条件的元素,存在就返回 true,不存在就返回 false。

换个角度思考,some() 也可以用来检测数组中的所有元素是否都不符合指定条件,都不符合的话就返回 false,有一个或者多个符合的话就返回 true。

查找数组中是否存在某个元素,如果存在就返回它在数组中的索引,如果不存在另做处理

let arr = ['apple', 'banana', 'orange'];
let num;
let flag = arr.some((item, index) => {
    if (item === 'banana') {
        num=index;
        return true
    }
})
if (flag) { // 如果存在
    console.log('存在,下角标为',num); // 存在,下角标为 1
} else {
    console.log('不存在,下角标为',num);
}

every()

全部符合后返回true 有一项不符合返回false

let arr = [32, 33, 16, 40];
let newArr = arr.every (function fn(age) {
    return age >= 18;
})
console.log(newArr); // false

reduce() 运算

reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

reduce() 可以作为一个高阶函数,用于函数的 compose。

let arr = [65, 44, 12, 4];
let newArr =  arr.reduce(function getSum(total, num) {
      return total + num;
})
console.log(newArr); // 125

reduceRight() 运算

从右到左,从数组的末尾向前将数组中的数组项做累加

let arr = [65, 44, 12, 4];
let newArr =  arr.reduceRight(function getSum(total, num) {
      return total + num;
})
console.log(newArr); // 125

from()

将类数组对象或可迭代对象转化为数组。

 // 利用Set()去重
let arr = [1,1,2,2,3,3];
let newArr = Array.from(new Set(arr))
console.log(newArr);  //输出[1,2,3]

for of 循环

keys() 下角标 values() 值

let arr = [1,5,50,4,40,50];
for(let val of arr){
    console.log(val); // 1 5 50 4 40 50
}

“use strict” 严格模式

"use strict";
function fn(){
   console.log(this);
   console.log(arguments);
}
fn(1,2,3);
// window.fn(1,2,3)

函数

函数形参默认赋值 function(x=1){}

arguments

function fn(){
    console.log(arguments);
}
fn(1,2,3);

声明函数 和表达式函数

区别:

声明函数:

① 函数声明必须包含名称

② 函数声明预解析时,函数部分会提升至作用域的顶部, 解析后就可以执行, 所以函数可以在声明之前可以被调用

function fn(){}
fc();

表达式函数:

① 函数表达式可以省略函数名称

② 函数表达式预解析时,函数部分不会提升,只有运行完赋值的函数部分,才能被调用,调用必须在表达式之后

var fun = function(aru){
    console.log('我是函数表达式');
    console.log(aru)
}
fun('pink');

箭头函数 ()=>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NN8ExNpp-1657280554684)(C:\Users\Timi\AppData\Roaming\Typora\typora-user-images\image-20220511121741133.png)]

注意:箭头函数 不能作为构造函数 不能使用new 没有原型prototype

let fn = (a) => {
    console.log("这是一个箭头函数",a)
}
fn(1); // 这是一个箭头函数 1

let fn2 = a => {
    console.log("参数只有一个值",a)
}
fn2(2) // 参数只有一个值 2

①在一行上面可以不写花括号 不写return 返回值

let fn3 = () =>"这是我的返回值";
console.log(fn3()); // 这是我的返回值

let fn4 = () => {
    return "这是我的返回值"
}
console.log(fn4()); // 这是我的返回值

②箭头函数没有arguments

let fn6 = () => {
    console.log(arguments)
}
fn6(1,12,4) // arguments is not defined

③有rest

let fn6 = (...a) =>{
    console.log(a)
}
fn6(1,12,4) // [1, 12, 4]

④this指向

1.指向绑定调用它的区域 window;

this指向定义它的对象( obj3)所指向的对象 (window )

let obj2 = {
    name:'kgc',
    age:18,
    show:() => {
        console.log(this);
        document.write('当前'+this.name+'的年龄是'+this.age);
    }
}
obj2.show() // this 指向window

2.this指向定义它的对象( show() )所指向的对象 ( obj3 )

let obj3 = {
    name:'kgc',
    age:18,
    show:function(){
        let fn = () => {
            document.write('当前'+this.name+'的年龄是'+this.age);
        }
        fn()
    }
}
obj3.show() // this 指向 obj3

es6 对象

Object.assign() 合并对象

属于浅拷贝

let obj = {name:'kgc',age:12};
let obj2 = {head:1,foot:2}
// let obj3 = {...obj,...obj2};
// console.log(obj3);
let obj4 = Object.assign(obj,obj2);
console.log(obj4);
console.log(obj);

Promise 对象

Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息

特点

1、对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:

  • pending: 初始状态,不是成功或失败状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。

2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。

Promise 创建

可以使用 new 来调用 Promise 的构造器来进行实例化

var promise = new Promise(function(resolve, reject) {
    // 异步处理
    // 处理结束后、调用resolve 或 reject
});

Promise 构造函数包含一个参数和一个带有 resolve(解析)和 reject(拒绝)两个参数的回调。在回调中执行一些操作(例如异步),如果一切都正常,则调用 resolve,否则调用 reject。

var myPromise = new Promise(function(resolve, reject){
    //当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...)
    //在本例中,我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法.
    setTimeout(function(){
        resolve("成功!"); //代码正常执行!
    }, 250);
    reject('失败!')
});
 //成功执行
myPromise.then((success)=>{
    //success的值是上面调用resolve(...)方法传入的值.
    //success参数不一定非要是字符串类型,这里只是举个例子
    return success+1
    document.write("Yay! " + successMessage);
}).then((success)=>{
    document.write("Yay! " + successMessage);
});
//失败执行
myPromise.catch(function(fail){
    document.write("NO! " + failMessage);
    
}) 

Promise 构造函数包含一个参数和一个带有 resolve(解析)和 reject(拒绝)两个参数的回调。在回调中执行一些操作(例如异步),如果一切都正常,则调用 resolve,否则调用 reject。

对于已经实例化过的 promise 对象可以调用 promise.then() 方法,传递 resolve 和 reject 方法作为回调。

promise.then() 是 promise 最为常用的方法。

promise简化了对error的处理,上面的代码我们也可以这样写:

![

可见Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tTdSGrQ9-1657280554685)(https://www.liaoxuefeng.com/files/attachments/1027242914217888/l)]

promise是什么

1、主要用于异步计算
2、可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
3、可以在对象之间传递和操作promise,帮助我们处理队列

promise的三个状态

1、对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:

  • pending: 初始状态,不是成功或失败状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。

Promise 优缺点

有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数(也就是回调地狱)。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

下面代码为回调地狱

img

使用promise封装ajax实例

function akax(url,data={}){
        return new Promise((resolve,reject)=>{
            var req = new XMLHttpRequest();
            req.open("GET",url,true);
            req.onload = function(){
                if (req.status === 200) {
                    resolve(req.responseText)
                }else{
                    reject(new Error(req.responseText))
                }
            }
            req.onerror = function(){
                reject(new Error(req.responseText))
            }
            req.send()
        })
    }

    ajax("data.json").then(res=>{
        console.log(res);
    })

上面代码中,resolve 方法和 reject 方法调用时,都带有参数。它们的参数会被传递给回调函数。reject 方法的参数通常是 Error 对象的实例,而 resolve 方法的参数除了正常的值以外,还可能是另一个 Promise 实例,比如像下面这样。

var p1 = new Promise(function(resolve, reject){
  // ... some code
});
 
var p2 = new Promise(function(resolve, reject){
  // ... some code
  resolve(p1);
})

上面代码中,p1 和 p2 都是 Promise 的实例,但是 p2 的 resolve 方法将 p1 作为参数,这时 p1 的状态就会传递给 p2。如果调用的时候,p1 的状态是 pending,那么 p2 的回调函数就会等待 p1 的状态改变;如果 p1 的状态已经是 fulfilled 或者 rejected,那么 p2 的回调函数将会立刻执行。

Promise then 链式调用

Promise.prototype.then 方法返回的是一个新的 Promise 对象,因此可以采用链式写法。

    let  promise  =  new  Promise((resolve,reject)=>{
          resolve(1)
    })
    promise.then(res=>{
         console.log(res); 
         return  res+1
    }).then(res=>{
         console.log(res);
         return  res+1
    }).then(res=>{
         console.log(res)
    })

上面的代码使用 then 方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。

如果前一个回调函数返回的是Promise对象,这时后一个回调函数就会等待该Promise对象有了运行结果,才会进一步调用。

    function ajax(url,data={}){
      return   new  Promise((resolve,reject)=>{
               var  req =  new  XMLHttpRequest();
               req.open("GET",url,true);
               req.onload = function(){
                  if(req.status ===200){
                       resolve(req.responseText)
                  }else{
                       reject(new  Error(req.responseText))
                  }
               };
               req.onerror =function(){
                  reject(new Error(req.statusText))
               };
               req.send()
          })
    }

    ajax("data.json").then(res=>{
         console.log(JSON.parse(res))
         return ajax("data.json")
    }).then(res=>{
        console.log(res)
    })

这种设计使得嵌套的异步操作,可以被很容易得改写,从回调函数的"横向发展"改为"向下发展"。

Promise.prototype.catch方法:捕捉错误

Promise.prototype.catch 方法是 Promise.prototype.then(null, rejection) 的别名,用于指定发生错误时的回调函数。

getJSON("/posts.json").then(function(posts) {
  // some code
}).catch(function(error) {
  // 处理前一个回调函数运行时发生的错误
  console.log('发生错误!', error);
});

Promise 对象的错误具有"冒泡"性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个 catch 语句捕获。

ajax("data.json").then(res=>{
         console.log(JSON.parse(res))
         return ajax("data.json")
    }).then(res=>{
        console.log(res)
    }).catch(err=>{
         console.log(err)
    })

Promise.all方法,Promise.race方法区别

Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

上面代码中,Promise.all 方法接受一个数组作为参数,p1、p2、p3 都是 Promise 对象的实例。(Promise.all 方法的参数不一定是数组,但是必须具有 iterator 接口,且返回的每个成员都是 Promise 实例。)

p 的状态由 p1、p2、p3 决定,分成两种情况。

  • (1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
  • (2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

下面是具体的案例

var   promise =  ["users","goods","data"].map((item,index)=>{
     return  ajax(item+".json")
})
Promise.all(promise).then(res=>{
     console.log(res)
})

Promise.race()

只要有一个promise状态改变 外层promise状态就会改变 率先改变的promise的返回值就会传递给外层的返回值

    function  axios(type=get,url,data={}){
        return   new  Promise((resolve,reject)=>{
            $.ajax({
                type:type,
                url:url,
                data:data,
                timeout:5000,
                success:function(res){
                    resolve(res)
                }
            })
        })
    }
    function timeout(){
        return  new Promise((resolve,reject)=>{
               setTimeout(function(){
                    resolve("请求超时")
               },1)
        })
    }
    Promise.race([timeout(),axios("get","data.json")]).then(res=>{
          console.log(res)
    })

async函数

async函数是用来取代回调函数的另一种方法

只要函数名之前加上async关键字,就表明该函数内部有异步操作。该异步操作应该返回一个Promise对象,前面用await关键字注明。当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncValue(value) {
  await timeout(50);
  return value;
}

上面代码中,asyncValue函数前面有async关键字,表明函数体内有异步操作。执行的时候,遇到await语句就会先返回,等到timeout函数执行完毕,再返回value。

async函数并不属于ES6,而是被列入了ES7,但是traceur编译器已经实现了这个功能。

promise setTimout 的区别

js是单线程语言,但js的宿主环境(比如浏览器,Node)是多线程的,宿主环境通过某种方式(事件驱动,下文会讲)使得js具备了异步的属性。

浏览器

js是单线程语言,浏览器只分配给js一个主线程,用来执行任务(函数),但一次只能执行一个任务,这些任务形成一个任务队列排队等候执行,但前端的某些任务是非常耗时的,比如网络请求,定时器和事件监听,如果让他们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,甚至导致页面的假死。所以,浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务是异步的。

任务队列

刚才说到浏览器为网络请求这样的异步任务单独开了一个线程,那么问题来了,这些异步任务完成后,主线程怎么知道呢?答案就是回调函数,整个程序是事件驱动的,每个事件都会绑定相应的回调函数,举个栗子,有段代码设置了一个定时器

setTimeout(function(){
    console.log(time is out);
}1000;

执行这段代码的时候,浏览器异步执行计时操作,当1000ms到了后,会触发定时事件,这个时候,就会把回调函数放到任务队列里。整个程序就是通过这样的一个个事件驱动起来的。
所以说,js是一直是单线程的,浏览器才是实现异步的那个家伙。

promise主任务是主线程执行 与log按顺序执行

img

导图要表达的内容用文字来表述的话:

  • 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
  • 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
  • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
  • 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

主线程

js一直在做一个工作,就是从任务队列里提取任务,放到主线程里执行。下面我们来进行更深一步的理解。

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步。

img

只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。

Event Loop

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。

主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。

异步任务有宏任务和微任务。

img

2.宏任务macrotask:

(事件队列中的每一个事件都是一个macrotask)

优先级:主代码块 > setImmediate > MessageChannel > setTimeout / setInterval

比如:setImmediate指定的回调函数,总是排在setTimeout前面

3.微任务包括:

优先级:process.nextTick > Promise > MutationObserver

下面这个代码输出结果是什么?

img

主程序和和settimeout都是宏任务,两个promise是微任务

第一个宏任务(主程序)执行完,执行全部的微任务(两个promise),再执行下一个宏任务

class类

原型

静态方法 直接 用Person.fn = function (){}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u172XCYR-1657280554688)(C:\Users\Timi\AppData\Roaming\Typora\typora-user-images\image-20220512100216991.png)]

没有函数提升

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bXZ7p275-1657280554688)(C:\Users\Timi\AppData\Roaming\Typora\typora-user-images\image-20220512094037346.png)]

static 静态方法 不能继承 用Person直接调用 首先加载的是静态方法 优化性能

class继承

cunstructor 里面有一个 super() 管道

静态方法 不能继承

继承的必须先执行super才能执行this

 class Person {
            // 构造器构造属性
            constructor(foot, head) {
                this.foot = foot;
                this.head = head;
            }
            // 方法
            show() {
                console.log(this.foot, this.head);
            }
            // 静态方法  首先加载的是静态方法
            static fn() {
                console.log('这是Person的静态方法');
            }
        }
        console.log(new Person(2, 1).foot);
        console.log(new Person(2, 1).head);
        Person.fn();
        console.log('---------');
        // Student 继承 Person   直接extends
        class Student extends Person {
            constructor(name, age, ...obj) {
                super(...obj);
                this.name = name;
                this.age = age;
            }
            show2() {
                super.show();
                console.log(this.name, this.age);
            }
            static fn2() {
                console.log('这是Student的静态方法');
            }
        }
        let student = new Student('张三', 18, 2, 1);
        student.show2();
        Student.fn2();  //静态方法不能继承  undefined

async

在return之前 提前返回 用async 和await

递归函数

instanceof 同来检测是否为 数组或者是对象

hansOwnProperty()判断对象中是否含有特定的属性 有返回true 没有返回false

深拷贝

// 使用深拷贝
        let obj = {
            name: 'kgc',
            age: 15,
            arr: [21, 5, 56, 8],
            show: undefined,
            num: null
        }
        let newObj = JSON.parse(JSON.stringify(obj));
        console.log(newObj); // 识别不到undefined 自动避开未拷贝show   可以识别到num 显示值为null 

使用递归函数实现深拷贝

 deepClone = (obj) => {
            if (typeof obj !== 'object') return;
            let newObj = obj instanceof Array ? [] : {}
            for (var key in obj) {
                if (obj.hasOwnProperty(key)) {
                    newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]
                }
            }
            return newObj
        }
        let newObj2 = deepClone(obj);
        console.log(newObj2);// 可以识别到show 可以识别到num 显示值为{}