es6第二天

95 阅读12分钟

1.整理数组新增方法

map方法:给数组中每一个元素 进行特殊处理后,返回一个新的数组 案例:

let prices=[20,30,40];

prices=prices.map((item)=>item+='元')

获取当前所有电影信息 let movies=[{id:1,name:"逃学威龙",imgUrl:"xxx.douban.com/1dda.png",n…}];

movies.map((item)=>{ item.imgUrl=item.imgUrl.replace('xxx','www') return item });

filter方法:过滤一个数组中,符合要求的元素,返回一个新数组 //搜索场景:数据量较小的时候 , 地址搜索 输出及格的同学的成绩 let counts=[40,50,80,100,20,45] counts.filter(item=>item>=60)

在数组的判断过程中,判断整体所有元素是否符合某一个条件时

  every: 只要有一个元素是假的,整体就返回假
  some:  只要有一个符合要求,整体就返回真
  

reduce 多对一 返回一个结果 将数组中的每一个数组项,进行对应操作,返回一个新的内容

prev :可以设置默认值 上次操作返回的结果 item: 当前进来数据 index:当前数据的索引值 array:当前操作的数组 默认情况:运行长度-1次,默认情况下,第一个数据自动作为 第二个数据的prev 案例: 如何添加默认参数 let arr=["百度","腾讯","阿里","字节跳动"] arr.reduce((prev,item)=>{ return <li>${item}</li>

},"")

数组的去重

let arr=["百度","腾讯","阿里","字节跳动","百度","腾讯","字节跳动","阿里"]

arr.reduce((prev,item)=>{ if(!prev.includes(item)){ prev.push(item) } return prev },[])

统计数组中每一个字符的出现次数

返回一个对象 字符 key 次数value {a:2,c:3}

let arr=["a","v","b","c","a","b","c","v","w"]; let result=arr.reduce((prev,item)=>{ if(prev in item){ prev[item]++; }else{ prev[item]=1; } return prev },{})

字符串 startsWith() 字符串以什么开头,返回布尔值 endsWith() 判断是否以对应的子串结尾 trim() 去除前后空格的 !!! 增加用户的体验性

新增数据类型

map类型:对象的升级版

原始的对象:key:value,key:value key只能是字符串 现阶段也可以了

map类型:key可以为任意类型的元素

let obj=new Map();//新建一个对象

obj[name]="1" let bt1=document.getElementById("bt1")

//.set方法 将特殊的变量,作为key obj.set(bt1,4)

//.get方法 获取set建立的变量值 console.log(obj.get(bt1))

symbol类型

//symbol 创建一个独一无二的值,防止出现命名冲突问题.通常用作对象的key

// 封装好的内容中,存在私有属性 私有:只和我对象有关 默认值,和用户无关

 let obj={ 
     addClass:function(){
                obj[name]
    },
     removeClass:""
 }
obj[name]="admin"
//obj.addClass()
 obj.name="123"
 

class 类

     构造函数:模拟类
     function People(name,age){
         this.name=name
       this.age=age
     }
     People.prototype.showInfo=function(){
         console.log(this.name)
    }
    如何定义一个类
    class people{
        constructor(name,age){
            this.name=name;
            this.age=age;
            this.height=100;
        }
        showInfo(){
            console.log(name)
        }
    }
    let p1=new People("王一",21);
    let p2=new People("王二",35);
    console.log(p1.showInfo == p2.showInfo);
    学生类 继承人类  class A extends B{} A继承B
    super()函数 父类的构造函数
    class student extends People{
        constructor(name,age,score){
            super(name,age);//调用父类的构造函数
            //super调用永远在最上面的
            this.score=score;
        }
        showInfo(){
        }
    }
    let s1=new Student("王一",21,99)
    console.log(s1);
    s1.showInfo()
    
    
    //利用类实现继承
    class People {
    constructor(name,age){
      this.name=name;
      this.age=age;
    }
  }
  class student extends people{
    constructor(name,age){
      super(name,age);
    }
    read(){
      console.log(`${this.name}正在读书`);
    }
  }
  class teacher extends people{
    constructor(name,age){
      super(name,age);
    }
    work(){
      console.log(`${this.name}正在教书`);
    }
  }
  let stu=new student("王三",21);
  let tea=new teacher("小红",35);
  stu.read();
  tea.work();
  
  //利用类重构卡牌
     class people {
    constructor(name) {
      this.name = name;
      this.lefthand = "";
      this.righthand = "";
     this.Card=function() {
        //定义牌
        let sizes = [
          "2",
          "3",
          "4",
          "5",
          "6",
          "7",
          "8",
          "9",
          "10",
          "J",
          "Q",
          "K",
          "A",
        ];
        let colors = ["黑桃", "红桃", "梅花", "方片"];
        this.size = sizes[parseInt(Math.random() * sizes.length)];
        this.color = colors[parseInt(Math.random() * colors.length)];
      };
    }
  
    grab() {
      //抓牌 给左手牌赋值一张给右手牌赋值一张
      let c1 = new this.Card();
      this.lefthand = c1.color + c1.size;
      let c2 =new this.Card();
      while (true) {
        
        if (c2.color + c2.size == c1.color + c1.size) {
          c2 =new this.Card();
        } else {
          break;
        }
        
      }
      this.righthand = c2.color + c2.size;
    }
    showCard() {
      //展示牌
      console.log("左手为" + this.lefthand, "右手为" + this.righthand);
    }
    ChangeCard() {
      //交换卡牌
      let card = this.lefthand;
      this.lefthand = this.righthand;
      this.righthand = card;
    }
  }
  let p1=new people("赌神")
  p1.grab();
  p1.showCard();
  p1.ChangeCard();
  p1.showCard();
  
  
  

1.什么是原型?什么是原型链?如何理解

原型: 原型分为隐式原型和显式原型,每个对象都有一个隐式原型,它指向自己的构

造函数的显式原型。

原型链: 多个__proto__组成的集合成为原型链

所有实例的__proto__都指向他们构造函数的 prototype

所有的 prototype 都是对象,自然它的__proto__指向的是 Object()的 prototype

所有的构造函数的隐式原型指向的都是 Function()的显示原型

Object 的隐式原型是 null
  
  

2.说一说 JS 中的常用的继承方式有哪些?以及各个继承方式的优缺点。

原型继承

// ----------------------方法一:原型继承

// 原型继承

// 把父类的实例作为子类的原型

// 缺点:子类的实例共享了父类构造函数的引用属性 不能传参

var person = {

friends: ["a", "b", "c", "d"]

}

var p1 = Object.create(person)

p1.friends.push("aaa")//缺点:子类的实例共享了父类构造函数的引用属性

console.log(p1);

console.log(person);//缺点:子类的实例共享了父类构造函数的引用属性
组合继承

// ----------------------方法二:组合继承

// 在子函数中运行父函数,但是要利用 call 把 this 改变一下,

// 再在子函数的 prototype 里面 new Father() ,使 Father 的原型中的方法也得到

继承,最后改变 Son 的原型中的 constructor

// 缺点:调用了两次父类的构造函数,造成了不必要的消耗,父类方法可以复用

// 优点可传参,不共享父类引用属性

function Father(name) {

this.name = name

this.hobby = ["篮球", "足球", "乒乓球"]

}

Father.prototype.getName = function () {

console.log(this.name);

}

function Son(name, age) {

Father.call(this, name)

this.age = age

}

Son.prototype = new Father()
Son.prototype.constructor = Son

var s = new Son("ming", 20)

console.log(s);

寄生组合继承

// ----------------------方法三:寄生组合继承

function Father(name) {

this.name = name

this.hobby = ["篮球", "足球", "乒乓球"]

}

Father.prototype.getName = function () {

console.log(this.name);

}

function Son(name, age) {

Father.call(this, name)

this.age = age

}

Son.prototype = Object.create(Father.prototype)

Son.prototype.constructor = Son

var s2 = new Son("ming", 18)

console.log(s2);
extend

// ----------------------方法四:ES6 的 extend(寄生组继承的语法糖)

//

子类只要继承父类,可以不写 constructor ,一旦写了,则在 constructor 中

的第一句话

// 必须是 super 。

class Son3 extends Father { // Son.prototype.__proto__ = Father.prototype

constructor(y) {

super(200) // super(200) => Father.call(this,200)

this.y = y

}

}

3.什么是内存泄漏?

内存泄露是指不再用的内存没有被及时释放出来,导致该段内存无法被使用就是内

存泄漏

4.为什么会导致的内存泄漏?

内存泄漏指我们无法在通过 js 访问某个对象,而垃圾回收机制却认为该对象还在被引

用,因此垃圾回收机制不会释放该对象,导致该块内存永远无法释放,积少成多,系

统会越来越卡以至于崩溃

5.垃圾回收机制都有哪些策略?

标记清除法

垃圾回收机制获取根并标记他们,然后访问并标记所有来自它们的引用,然后在访问

这些对象并标记它们的引用…如此递进结束后若发现有没有标记的(不可达的)进行

删除,进入执行环境的不能进行删除

引用计数法

当声明一个变量并给该变量赋值一个引用类型的值时候,该值的计数+1,当该值赋值

给另一个变量的时候,该计数+1,当该值被其他值取代的时候,该计数-1,当计数变

为 0 的时候,说明无法访问该值了,垃圾回收机制清除该对象

6.深拷贝和浅拷贝 手写浅拷贝深拷贝

// ----------------------------------------------浅拷贝

// 只是把对象的属性和属性值拷贝到另一个对象中

var obj1 = {

a: {

a1: { a2: 1 },

a10: { a11: 123, a111: { a1111: 123123 } }

},

b: 123,

c: "123"

}

// 方式 1

function shallowClone1(o) {

let obj = {}

for (let i in o) {

obj[i] = o[i]

}

return obj

}

// 方式 2
// 方式 3

var shallowObj3 = Object.assign({}, obj1)

let shallowObj = shallowClone1(obj1);

shallowObj.a.a1 = 999

shallowObj.b = true

console.log(obj1); //第一层的没有被改变,一层以下就被改变了

// ----------------------------------------------深拷贝

// 简易版

function deepClone(o) {

let obj = {}

for (var i in o) {

// if(o.hasOwnProperty(i)){

if (typeof o[i] === "object") {

obj[i] = deepClone(o[i])

} else {

obj[i] = o[i]

}

// }

}

return obj
var myObj = {

a: {

a1: { a2: 1 },

a10: { a11: 123, a111: { a1111: 123123 } }

},

b: 123,

c: "123"

}

var deepObj1 = deepClone(myObj)

deepObj1.a.a1 = 999

deepObj1.b = false

console.log(myObj);

// 简易版存在的问题:参数没有做检验,传入的可能是 Array、null、regExp、

Date

function deepClone2(o) {

if (Object.prototype.toString.call(o) === "[object Object]") { //检测是否为对

象

let obj = {}

for (var i in o) {

if (o.hasOwnProperty(i)) {

if (typeof o[i] === "object") {
obj[i] = deepClone(o[i])

} else {

obj[i] = o[i]

}

}

}

return obj

} else {

return o

}

}

function isObject(o) {

return Object.prototype.toString.call(o) === "[object Object]" ||

Object.prototype.toString.call(o) === "[object Array]"

}

// 继续升级,没有考虑到数组,以及 ES6 中的 map、set、weakset、weakmap

function deepClone3(o) {

if (isObject(o)) {//检测是否为对象或者数组

let obj = Array.isArray(o) ? [] : {}

for (let i in o) {

if (isObject(o[i])) {
obj[i] = deepClone(o[i])

} else {

obj[i] = o[i]

}

}

return obj

} else {

return o

}

}

// 有可能碰到循环引用问题 var a = {}; a.a = a; clone(a);//会造成一个死循环

// 循环检测

// 继续升级

function deepClone4(o, hash = new map()) {

if (!isObject(o)) return o//检测是否为对象或者数组

if (hash.has(o)) return hash.get(o)

let obj = Array.isArray(o) ? [] : {}

hash.set(o, obj)

for (let i in o) {

if (isObject(o[i])) {
obj[i] = deepClone4(o[i], hash)

} else {

obj[i] = o[i]

}

}

return obj

}

// 递归易出现爆栈问题

// 将递归改为循环,就不会出现爆栈问题了

var a1 = { a: 1, b: 2, c: { c1: 3, c2: { c21: 4, c22: 5 } }, d: 'asd' };

var b1 = { b: { c: { d: 1 } } }

function cloneLoop(x) {

const root = {};

// 栈

const loopList = [ //->[]->[{parent:{a:1,b:2},key:c,data:{ c1: 3, c2: { c21: 4,c22: 5 } }}]

{

parent: root,

key: undefined,

data: x,

}
];

while (loopList.length) {

// 深度优先

const node = loopList.pop();

const parent = node.parent; //{} //{a:1,b:2}

const key = node.key; //undefined //c

const data = node.data; //{ a: 1, b: 2, c: { c1: 3, c2: { c21: 4, c22: 5 } }, d:

'asd' } //{ c1: 3, c2: { c21: 4, c22: 5 } }}

// 初始化赋值目标,key 为 undefined 则拷贝到父元素,否则拷贝到子元素

let res = parent; //{}->{a:1,b:2,d:'asd'} //{a:1,b:2}->{}

if (typeof key !== 'undefined') {

res = parent[key] = {};

}

for (let k in data) {

if (data.hasOwnProperty(k)) {

if (typeof data[k] === 'object') {

// 下一次循环

loopList.push({

parent: res,

key: k,

data: data[k],

})

} else {

res[k] = data[k];

}

}

}

}

return root

}

function deepClone5(o) {

let result = {}

let loopList = [{parent: result,key: undefined,data: o}]

while (loopList.length) {

let node = loopList.pop()

let { parent, key, data } = node

let anoPar = parent

if (typeof key !== 'undefined') {
anoPar = parent[key] = {}

}

for (let i in data) {

if (typeof data[i] === 'object') {

loopList.push({

parent: anoPar,

key: i,

data: data[i]

})

} else {

anoPar[i] = data[i]

}

}

}

return result

}

let cloneA1 = deepClone5(a1)

cloneA1.c.c2.c22 = 5555555

console.log(a1);

console.log(cloneA1);

// ------------------------------------------JSON.stringify()实现深拷贝

function cloneJson(o) {
return JSON.parse(JSON.stringify(o))

}

// let obj = { a: { c: 1 }, b: {} };

// obj.b = obj;

// console.log(JSON.parse(JSON.stringify(obj))) // 报错 // Converting circular

structure to JSON

深拷贝能使用 hash 递归的方式写出来就可以了

不过技多不压身,推荐还是看一看使用 while 实现深拷贝方法。

7.为什么 JS 是单线程的?

 因为 JS 里面有可视的 Dom,如果是多线程的话,这个线程正在删除 DOM 节点,另

一个线程正在编辑 Dom 节点,导致浏览器不知道该听谁的

8.如何实现异步编程?

 回调函数

9.Generator 是怎么样使用的以及各个阶段的变化如何?

 首先生成器是一个函数,用来返回迭代器的

调用生成器后不会立即执行,而是通过返回的迭代器来控制这个生成器的一步一步执

行的

通过调用迭代器的 next 方法来请求一个一个的值,返回的对象有两个属性,一个是

value,也就是值;另一个是 done,是个布尔类型,done 为 true 说明生成器函数执

行完毕,没有可返回的值了,

done 为 true 后继续调用迭代器的 next 方法,返回值的 value 为 undefined

状态变化:

每当执行到 yield 属性的时候,都会返回一个对象

这时候生成器处于一个非阻塞的挂起状态

调用迭代器的 next 方法的时候,生成器又从挂起状态改为执行状态,继续上一次的执

行位置执行

直到遇到下一次 yield 依次循环

直到代码没有 yield 了,就会返回一个结果对象 done 为 true,value 为 undefined

10.说说 Promise 的原理?你是如何理解 Promise 的?

做到会写简易版的 promise 和 all 函数就可以

class MyPromise2 {

constructor(executor) {

// 规定状态

this.state = "pending"

// 保存 `resolve(res)` 的 res 值

this.value = undefined

// 保存 `reject(err)` 的 err 值

this.reason = undefined

// 成功存放的数组

this.successCB = []

// 失败存放的数组

this.failCB = []

let resolve = (value) => {

if (this.state === "pending") {

this.state = "fulfilled"

this.value = value

this.successCB.forEach(f => f())
}

}

let reject = (reason) => {

if (this.state === "pending") {

this.state = "rejected"

this.value = value

this.failCB.forEach(f => f())

}

}

try {

// 执行

executor(resolve, reject)

} catch (error) {

// 若出错,直接调用 reject

reject(error)

}

}

then(onFulfilled, onRejected) {

if (this.state === "fulfilled") {

onFulfilled(this.value)

}

if (this.state === "rejected") {
onRejected(this.value)

}

if (this.state === "pending") {

this.successCB.push(() => { onFulfilled(this.value) })

this.failCB.push(() => { onRejected(this.reason) })

}

}

}

Promise.all = function (promises) {

let list = []

let count = 0

function handle(i, data) {

list[i] = data

count++

if (count == promises.length) {

resolve(list)

}

}

return Promise((resolve, reject) => {

for (let i = 0; i < promises.length; i++) {
promises[i].then(res => {

handle(i, res)

}, err => reject(err))

}

})

}

11.宏任务和微任务都有哪些?

宏任务:script、setTimeOut、setInterval、setImmediate

微任务:promise.then,process.nextTickObject.observeMutationObserver

注意:Promise 是同步任务

12.宏任务和微任务都是怎样执行的?

执行宏任务 script,

进入 script 后,所有的同步任务主线程执行

所有宏任务放入宏任务执行队列

所有微任务放入微任务执行队列

先清空微任务队列,

再取一个宏任务,执行,再清空微任务队列

依次循环

例题 1

setTimeout(function(){

console.log('1')

});

new Promise(function(resolve){

console.log('2');

resolve();

}).then(function()

console.log('3')

});

console.log('4');

new Promise(function(resolve){

console.log('5');
resolve();

}).then(function(){

console.log('6')

});

setTimeout(function(){

console.log('7')

});

function bar(){

console.log('8')

foo()

}

function foo(){

console.log('9')

}

console.log('10')

bar()
解析

首先浏览器执行 Js 代码由上至下顺序,遇到 setTimeout,把 setTimeout 分发到宏

任务 Event Queuenew Promise 属于主线程任务直接执行打印 2

Promis 下的 then 方法属于微任务,把 then 分到微任务 Event Queueconsole.log(‘

4’)属于主线程任务,直接执行打印 4
又遇到 new Promise 也是直接执行打印 5Promise 下到 then 分发到微任务 Event

Queue 中

又遇到 setTimouse 也是直接分发到宏任务 Event Queue 中,等待执行

console.log(‘

10’)属于主线程任务直接执行

遇到 bar()函数调用,执行构造函数内到代码,打印 8,在 bar 函数中调用 foo 函数,

执行 foo 函数到中代码,打印 9

主线程中任务执行完后,就要执行分发到微任务 Event Queue 中代码,实行先进先出,

所以依次打印 36

微任务 Event Queue 中代码执行完,就执行宏任务 Event Queue 中代码,也是先进

先出,依次打印 17。

最终结果:24510893617

例题 2

setTimeout(() => {

console.log('1');

new Promise(function (resolve, reject) {

console.log('2');

setTimeout(() => {

console.log('3');

}, 0);

resolve();

}).then(function () {

console.log('4')
})

}, 0);

console.log('5'); //5 7 10 8 1 2 4 6 3

setTimeout(() => {

console.log('6');

}, 0);

new Promise(function (resolve, reject) {

console.log('7');

// reject();

resolve();

}).then(function () {

console.log('8')

}).catch(function () {

console.log('9')

})

console.log('10');

运行结果: 5 7 10 8 1 2 4 6 3

13.变量和函数怎么进行提升的?优先级是怎么样的?

对所有函数声明进行提升(除了函数表达式和箭头函数),引用类型的赋值

开辟堆空间

存储内容

将地址赋给变量

对变量进行提升,只声明,不赋值,值为 undefined

14.var let const 有什么区别?

var

var 声明的变量可进行变量提升,letconst 不会

var 可以重复声明

var 在非函数作用域中定义是挂在到 window 上的

let

let 声明的变量只在局部起作用

let 防止变量污染

不可在声明

const

具有 let 的所有特征

不可被改变

如果使用 const 声明的是对象的话,是可以修改对象里面的值的

15.箭头函数和普通函数的区别?箭头函数可以当做构造函数 new 吗?

箭头函数是普通函数的简写,但是它不具备很多普通函数的特性

第一点,this 指向问题,箭头函数的 this 指向它定义时所在的对象,而不是调用时所

在的对象

不会进行函数提升

没有 arguments 对象,不能使用 arguments,如果要获取参数的话可以使用 rest 运

算符

没有 yield 属性,不能作为生成器 Generator 使用

不能 new

没有自己的 this,不能调用 call 和 apply

没有 prototype,new 关键字内部需要把新对象的_proto_指向函数的 prototype