ES6及JS基础总结

Promise

1、介绍下Promise

Promise 是异步编程的一种解决方案,简单说就是一个保存着某个未来才会结束的事件(通常是一个异步操作)结果的容器。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
它有三种状态,pending(进行中)、fulfilled(已成功)、reject(已失败)。注:resolved是指完成状态,结果可能包含fulfilled和rejected

  • 状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态;
  • 一旦状态改变,就不会再变,只有两种可能:从pending变为fulfilled和从pending变为rejected
  • 无法取消,一旦新建它就会立即执行,无法中途取消。
  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  • 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
2、Promise为了解决什么问题

使用Promise的语法来解决回调地狱的问题,使代码拥有可读性和可维护性。
“回调函数”:把一个函数当作参数传递,传递的是函数的定义并不会立即执行,而是在将来特定的时机再去调用,这个函数就叫做回调函数。
“回调地狱”:把函数作为参数层层嵌套请求,这样层层嵌套,人们称之为回调地狱,代码阅读性非常差。

var sayhello = function (order, callback) {
	setTimeout(function () {
    console.log(order);
    callback();
  }, 1000);
}
sayhello("first", function () {
  sayhello("second", function () {
    sayhello("third", function () {
      console.log("end");
    });
  });
});

使用promise改造

ar sayhello = function (order) {
  return new Promise(function (resolve, reject) {
      setTimeout(function () {
      console.log(order);
      //在异步操作执行完后执行 resolve() 函数
      resolve();  
    }, 1000);
  });
}
sayhello("first").then(function () {
  //仍然返回一个 Promise 对象
  return sayhello("second");  
}).then(function () {
  return sayhello("third");
}).then(function () {
  console.log('end');
}).catch(function (err) {
  console.log(err);
})

从表面上看,Promise只是能够简化层层回调的写法,而实质上Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback 函数要简单、灵活的多。
通过Promise这种方式很好的解决了回调地狱问题,使得异步过程同步化,让代码的整体逻辑与大脑的思维逻辑一致,减少出错率。

3、Promise提供的方法
  • Promise.prototype.then()
    它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
    then方法返回的是一个新的Promise实例**(注意,不是原来那个Promise实例)**。因此可以采用链式写法,即then方法后面再调用另一个then方法。
  • Promise.prototype.catch(),用于指定发生错误时的回调函数
  • Promise.prototype.finally(),不管 Promise 对象最后状态如何,都会执行的操作,不接受任何参数所以无法知道状态
    finally本质上是then方法的特例。
promise
.finally(() => {
  // 语句
});

// 等同于
promise
.then(
  result => {
    // 语句
    return result;
  },
  error => {
    // 语句
    throw error;
  }
);

实现:

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

上面代码中,不管前面的 Promise 是fulfilled还是rejected,都会执行回调函数callback。

  • Promise.all(),用于将多个 Promise 实例,包装成一个新的 Promise 实例
    如果传入的参数不是Promise,会调用Promise.resolve方法转成Promise实例
    状态变更:
    只有所有promise的状态变更为fulfilled,新的Promise实例才会变成fulfilled
    只要其中一个被rejected,新的Promise实例也会变成reject,此时第一个被reject的实例的返回值,会传递给p的回调函数
  • Promise.race()
    它和Promise.all()的区别是:只要其中有一个实例率先改变状态,新的Promise实例就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给新的Promise实例的回调函数。
  • Promise.allSettled()
    只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),返回的 Promise 对象才会发生状态变更。
  • Promise.any()
    只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
  • Promise.resolve()
    将现有对象转为 Promise 对象
  • Promise.reject()
    Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
4、手写Promise

(详见手写promise)[https://developer.aliyun.com/article/613412]
(结合阮一峰的promise和class介绍一起看理解会更深刻一些)[https://es6.ruanyifeng.com/#docs/promise]

class Promise {
  // constructor为构造方法,通过new命令生成对象实例时,自动调用该方法
  // this是实例对象
  constructor(executor) {
    // 初始状态
    this.state = 'pending'
    // 成功的返回值
    this.value = undefined
    // 失败原因
    this.reason = undefined
    // 成功存放的数组
    this.onResolvedCallbacks = []
    // 失败存放的数组
    this.onRejectCallbacks = []

    const resolve = value => {
      // 调用resolved后,状态需要变更
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value
        // 一旦执行resolve,调用成功数组的函数
        this.onResolvedCallbacks.forEach(fn => fn())
      }
    }
    const reject = reason => {
      // 调用resolved后,状态变更为失败
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.reason = reason
        this.onRejectCallbacks.forEach(fn => fn())
      }
    }
    try {
      executor(resolve, reject)
    } catch (err) {
      // 如果执行错误,直接把错误返回
      reject(err)
    }
  }
  // then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
  then (onFulfilled, onRejected) {
    // 如果onFulfilled不是函数,直接返回value
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err}
    // 如果状态为成功,执行onFulfilled,传入成功值
    let promise2 = new Promise((resolve, reject)=> {
      // onFulfilled和onReject只能异步调用
      if (this.state === 'fulfilled') {
        setTimeout(()=> {
          try{
            let x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch(err) {
            reject(err)
          }
        },0)
      } else if (this.state === 'rejected') {
        setTimeout(()=> {
          try{
            let x = onFulfilled(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch(err) {
            reject(err)
          }
        },0)
      } else if (this.state === 'pending') {
        this.onResolvedCallbacks.push(()=> {
          setTimeout(()=> {
            try{
              let x = onFulfilled(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch(err) {
              reject(err)
            }
          },0)
        })
        this.onRejectCallbacks.push(()=> {
          setTimeout(()=> {
            try{
              let x = onFulfilled(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch(err) {
              reject(err)
            }
          },0)
        })
      }
    })
    return promise2
  }
  // Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
  catch() {

  }
}

function resolvePromise(promise2, x, resolve, reject) {
  // x不能等于promise2,会循环饮用报错
  if (x === promise2) {
    return reject(new TypeError('Chaining cycle detected for promise'))
  }
  // 防止重复调用
  let called
  // 如果为普通类型直接resolve
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then
      // then是函数,默认为promise
      if (typeof then === 'function') {
        then.call(x,y => {
          if (called) return 
          called = true
          // resolve的结果依旧是promise 那就继续解析
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          if (called) return 
          called = true
          reject(err)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    resolve(x)
  }
}

// resolve、catch、reject、race、all方法不在promise/A+规范中,均为ES6实现的方法

Promise.resolve = function (val) {
  return new Promise((resolve, reject)=> {
    resolve(val)
  })
}

Promise.reject = function (val) {
  return new Promise((resolve, reject)=>{
    reject(val)
  })
}

// 第一个完成的promise函数状态传递给Promise.race的结果
Promise.race = function (promises = []) {
  return new Promise((resolve, reject) => {
    promises.forEach(fn => {
      // 如果传入的参数为promise
      if (fn && typeof fn === 'function') {
      // 谁先执行完到then,使用第一个执行完成的结果
      fn.then(resolve, reject) // 这里不理解可以看看前面then的实现
      } else {
        Promise.resolve(fn).then(resolve, reject)
      }
    })
  })
}

Promise.all = function (promises = []) {
  const promiseRes = []
  // 主要需要实现两个功能,1、所有promise的状态都变成fulfilled才会变成fulfilled,其中要一个被rejected,状态就会变成reject;
  // 2、返回的参数是按照传入的顺序而不是完成的时间先后
  return new Promise((resolve, reject) => {
    promises.forEach((fn, index) => {
      // 可以加入传入参数不为promise的判断处理,见race方法的实现
      fn.then(res => {
        promiseRes.splice(index, 0, res)
        if (promiseRes.length = promises.length) {
          resolve(promiseRes)
        }
      }, err => {
        reject(err)
      })
    })
  })
}
5、promise怎么实现的异步队列,怎么实现的链式调用

理解了上面的手写promise,就可以轻易回答这个问题
异步队列:
promise 的本质是回调函数,then 方法的本质是依赖收集,它把 fulfilled 状态要执行的回调函数放在一个队列, rejected 状态要执行的回调函数放在另一个队列。待 promise 从 pending 变为 fulfilled/rejected 状态后,把相应队列的所有函数,执行一遍。
链式调用:
then方法返回的是一个新的Promise实例。所以可以采用链式写法,将返回结果作为参数,传入第二个回调函数

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});
6、await和promise的关系,分别的应用场景有哪些

async 函数是 Generator 函数的语法糖,是一种异步编程解决方案。
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
异步函数的语法结构更像是标准的同步函数,发明了async和await的初衷就是让异步代码的语法结构跟同步代码类似。相对promise,async的实现最简洁,最符合语义
应用场景:await能解决的问题,promise其实也可以,但是在一些简单的异步场景,await会更加简洁,更具语义化;另外promise提供了很多方法,比如all,race这些能满足更多场景的使用

7、promise 有几种状态,Promise 有什么优缺点

有三种状态,pengding、fulfilled、rejected。
优点:
(1)解决回调地狱问题 (2)更好地进行错误捕获 (3)提高代码可读性
缺点:
(1)无法取消Promise,一旦新建它就会立即执行,无法中途取消。
(2)如果不设置回调函数,promise内部抛出的错误,不会反应到外部。
(3)当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

8、如何实现 Promise.finally

不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};
9、async/await了解么,generator用过么

generator:
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。ES6 诞生以前,异步编程的方法大概有四种,回调函数、事件监听、发布/订阅、Promise 对象。Generator 函数将 JavaScript 异步编程带入了一个全新的阶段。
语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态
遍历器对象的next方法的运行逻辑如下。
(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。
for…of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法。
async/await:
async 其实就是 Generator 函数的语法糖。将 Generator 函数的星号(*)替换成async,将yield替换成await
async函数对 Generator 函数的改进,体现在以下四点。
1)内置执行器。Generator 函数的执行必须靠执行器,await会自动执行
2) 更好的语义。比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
3)更广的适用性。await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)
4)返回值是 Promise。async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了

10、如何限制 Promise 请求并发数

事件循环机制

详见
事件循环分为浏览器事件循环和node.js事件循环
浏览器的事件循环分为同步任务和异步任务;所有同步任务都在主线程上执行,形成一个函数调用栈(执行栈),而异步则先放到任务队列(task queue)里,任务队列又分为宏任务(macro-task)与微任务(micro-task)。下面的整个执行过程就是事件循环

宏任务大概包括::script(整块代码)、setTimeout、setInterval、I/O、UI交互事件、setImmediate(node环境)

微任务大概包括::new promise().then(回调)、MutationObserver(html5新特新)、Object.observe(已废弃)、process.nextTick(node环境)
若同时存在promise和nextTick,则先执行nextTick
执行过程
JS 引擎去执行 JS 代码的时候会从上至下按顺序执行,先把同步任务放入执行栈中立即执行,微任务放入微任务队列,宏任务放在宏任务队列。当执行栈被清空,然后去执行所有的微任务,当所有微任务执行完毕之后。再次从宏任务开始循环执行,直到执行完毕,然后再执行所有的微任务,就这样一直循环下去。如果在执行微队列任务的过程中,又产生了微任务,那么会加入整个队列的队尾,也会在当前的周期中执行。其实浏览器执行Js代码的完整顺序应该是:同步任务 ——> 异步微任务 ——> DOM渲染页面 ——>异步宏任务

1、nextTick,setTimeout和setImmediate的区别

process.nextTick(),效率最高,消费资源小,但会阻塞CPU的后续调用; process.nextTick()方法可以在当前"执行栈"的尾部–>下一次Event Loop(主线程读取"任务队列")之前–>触发process指定的回调函数。也就是说,它指定的任务总是发生在所有异步任务之前,当前主线程的末尾。(nextTick虽然也会异步执行,但是不会给其他io事件执行的任何机会)
setTimeout(),精确度不高,可能有延迟执行的情况发生,且因为动用了红黑树,所以消耗资源大; setTimeout()只是将事件插入了"任务队列",必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。
setImmediate(),消耗的资源小,也不会造成阻塞,但效率也是最低的。setImmediate()是将事件插入到事件队列尾部,主线程和事件队列的函数执行完成之后立即执行setImmediate指定的回调函数,和setTimeout(fn,0)的效果差不多,但是当他们同时在同一个事件循环中时,执行顺序是不定的。
(参考)[https://blog.csdn.net/qq_42033567/article/details/108129645]
(部分题目)[https://blog.csdn.net/IT_studied/article/details/124758936]

2、执行顺序题目

1、

console.log('1')
setTimeout(() => {
    console.log('4')
    setTimeout(() => {
        console.log('7')
    }, 0)
   Promise.resolve()
     .then(() => {
         console.log('6')
     })
    console.log('5')
}, 0)
Promise.resolve()
    .then(() => {
        console.log('3')
    })
console.log('2')

输出 1,2,3,4,5,6,7
2、

console.log(1);
setTimeout(()=>console.log(2));
new Promise((resolve, reject)=>{
  Promise.resolve(3).then((result)=>{
    console.log(result);
  });
  resolve();
  console.log(4);
}).then((result)=>{
  console.log(result);
}, (error)=>{
  console.log(error);
});
console.log(5);

输出:1 4 5 3 undefined 2
3、

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
})
const promise2 = promise1.then(() => {
  throw new Error('error!!!')
})
 
console.log('promise1', promise1)
console.log('promise2', promise2)
 
setTimeout(() => {
  console.log('promise1', promise1)
  console.log('promise2', promise2)
}, 2000)

输出:

promise1 Promise {  }
promise2 Promise {  }
(node:50928) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: error!!!
(node:50928) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
promise1 Promise { 'success' }
promise2 Promise {
   Error: error!!!
    at promise.then (...)
    at  }

4、

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('once')
    resolve('success')
  }, 1000)
})
 
const start = Date.now()
promise.then((res) => {
  console.log(res, Date.now() - start)
})
promise.then((res) => {
  console.log(res, Date.now() - start)
})

输出: (答案不唯一,promise forEach消耗的时间会有差异)
once
success 1002
success 1002
5、

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

输出:1(.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。)
6、

const promise = new Promise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    console.log("timerStart");
    resolve("success");
    console.log("timerEnd");
  }, 0);
  console.log(2);
});
promise.then((res) => {
  console.log(res);
});
console.log(4);

输出:1、2、4、timerStart、timerEnd、success

ES6

1、ES6 语法用过哪些,都有哪些常用的特性

  • let、const;1、不存在变量提升;2、封闭作用域,所在的代码块内有效;3、暂时性死区”,在代码块内,使用let命令声明变量之前,该变量都是不可用的。4、不允许重复声明;
    const实际上并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动,即不能改动简单类型的数据(数值、字符串、布尔值),可以改动复合类型的数据(主要是对象和数组)。多使用const,有利于提高程序的运行效率
  • 变量解构;指按照一定模式,从 数组和对象中提取值,对变量进行赋值。
  • 模板字符串;用反引号(`)标识 ,将变量名写在${}之中
  • 箭头函数;
  • 链判断运算符?.;Null 判断运算符; 0 || 1,算符左侧的值为null或undefined时,才会返回右侧的值。
  • symbol;通过Symbol()函数生成一个独一无二的值,可以保证对象属性名不会产生冲突。只能用Object.getOwnPropertySymbols()方法遍历出symbol值
  • Set、Map;Set类似于数组,但是成员的值都是唯一的。Map类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键
  • Proxy;可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写;一共支持 13 种拦截操作,常用的是get,set;proxy相较ES5Object.defineProperty的不同,1、直接监听对象⽽⾮属性 2、直接监听数组的变化 3、拦截⽅式较多(有 13 种⽅式)4、Proxy 返回⼀个新对象,可以只操作新对象⽬的,⽽ Object.defineProperty 只能遍历对象属性
  • Promise;异步方程的解决方案
  • async; 是generator函数的语法糖,使异步操作变得更方便
  • class,ES6的类,完全可以看作构造函数的另一种写法。
  • module;1、可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。2、模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。3、import命令具有提升效果,会提升到整个模块的头部,首先执行4、ES6 module是编译时加载 5、CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。6、AMD是为了解决CommonJS只能同步加载而诞生的标准。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。也采用require()语句加载模块,但是多两个参数,第一个是加载的模块,第二个是加载成功后回调的函数require([module], callback)。
    2、ES6和ES5的继承
    ES5和ES6继承
    JS原型链与继承别再被问倒了
    一般有构造函数继承、原型链继承、组合式继承、寄生式继承、组合继承式继承
    组合式继承:组合继承是 JavaScript 最常用的继承模式; 不过, 它也有自己的不足. 组合继承最大的问题就是无论什么情况下,都会调用两次父类构造函数: 一次是在创建子类型原型的时候, 另一次是在子类型构造函数内部. 寄生组合式继承就是为了降低调用父类构造函数的开销而出现的 。
function Parent(name) {
    this.name = name || 'parent';
}
function Child(name, age) {
    Parent.call(this, name); //继承实例属性,第一次调用Parent()
    this.age = age;
}
Child.prototype = new Parent(); //继承原型,第二次调用Parent()
Child.prototype.constructor = Child;//修正构造函数为自己本身

寄生组合式继承:所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

function extend(subClass, superClass) {
    var prototype = Object(superClass.prototype); // 创建对象,创建父类原型的一个副本
    prototype.constructor = subClass; // 增强对象,弥补因重写原型而失去的默认的constructor 属性
    subClass.prototype = prototype; // 指定对象,将新创建的对象赋值给子类的原型
}
function Parent(name) {
    this.name = 'allen';
}
function Child(name) {
    Parent.call(this, name); //继承第一步,继承实例属性,调用Parent()
}
extend(Child, Parent); //继承第二步,不会调用Parent()

3、箭头函数与普通函数的区别

  • 箭头函数没有自己的this对象(详见下文 )。
  • 不可以当作构造函数,也就是说,不可 以对箭头函数使用new命令,否则会抛出一个错误。
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

JS基础

1、js的数据类型都有哪些 ,有什么区别,数据类型常用的判断方式都有哪些,为什么基本数据类型存到栈但是引用数据类型存到堆
基础数据类型:Number、String、Boolean、Null、Undefined、Symbol
引用数据类型:Object、Array、Function
常用判断方式:

  • typeof,由于历史原因typeof null和[]返回的都是是object,另外判断值是否声明用typeof 变量 === “undefined”
  • object.property.toString.call方法 ,返回"[object, 类型],适用于所有数据类型判断
  • instanceof,只能用于判断复杂数据类型
    基本数据类型是指存放在栈中的简单数据段,数据大小确定,内存空间大小可以分配,它们是直接按值存放的,所以可以直接按值访问。
    引用类型是存放在堆内存中的对象,变量其实是保存的在栈内存中的一个指针(保存的是堆内存中的引用地址),这个指针指向堆内存。引用数据类型数据创建的时候大小不确定。
    堆比栈大,栈比堆的运算速度快,对象是一个复杂的结构,并且可以自由扩展,将他们放在堆中是为了不影响栈的效率。简单数据类型就比较稳定,并且它只占据很小的内存,放在栈中能提高效率

2、闭包
闭包就是能够读取其他函数内部变量的函数。可以理解成“定义在一个函数内部的函数“。当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
特点:

  • 闭包会使函数中的变量都保存在内存中,不会被垃圾回收机制回收,占用内存导致内存泄露;解决方法是,在退出函数之前,将不使用的局部变量全部删除。
    学习Javascript闭包

3、原型链讲一下
当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__(隐式原型)属性所指向的那个对象(可以理解为父对象)里找,如果父对象也不存在这个属性,则继续往父对象的__proto__属性所指向的那个对象(可以理解为爷爷对象)里找,这种通过__proto__属性来连接对象直到null的一条链即为我们所谓的原型链。
原型链详解
ES6及JS基础总结_第1张图片
4、esmodule和commonjs区别是什么,还接触过其他的模块化方案么

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  • CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段

6、设计模式
前端比较常见的是单例模式、观察者模式、代理模式。
单例模式:指保证一个类仅有一个实例,并提供一个访问它的全局访问点。常见是用于命名空间;Vuex、Redux也采纳了单例模式,两者都用一个全局的惟一Store来存储所有状态。

  • 单例模式能保证全局的唯一性,可以减少命名变量
  • 单例模式在一定情况下可以节约内存,减少过多的类生成需要的内存和运行时间
  • 把代码都放在一个类里面维护,实现了高内聚
    观察者模式:定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。比如当数据有更新,changed 方法会被调用
  • 观察者和被观察者是 抽象耦合 的
  • 建立一套触发机制
    代理模式:指为一个原对象找一个代理对象,以便对原对象进行访问。即在访问者与目标对象之间加一层代理,通过代理做授权和控制。事件委托/代理,ES6 的 proxy 都是这一模式的实现。
  • 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
  • 远程代理使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
  • 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
  • 保护代理可以控制对真实对象的使用权限。

7、process.env.NODE_ENV是什么?说一下 Process ,以及 Require 原理?
在node中,有全局变量process表示的是当前的node进程。
process.env包含着关于系统环境的信息,但是process.env中并不存在NODE_ENV。
NODE_ENV是一个用户自定义的变量,在webpack中它的用途是判断生产环境或开发环境。

8、Object.create(null)和直接创建一个{}有什么区别
Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型。
Object.create(null) 创建一个空对象,此对象无原型方法。
{} 其实是new Object(),具有原型方法。

10、异步加载js的方式都有哪些
defer,始终在页面渲染后(dom树生成后)再执行js
async,js加载完后立即执行,可能会阻塞渲染
按需加载


11、判断一个对象是否是循环引用对象
循环引用是指对象的地址和源的地址相同,它只会发生在Object等引用类型的数据中

// 循环引用
const a = {};
a.b = a

实现一个方法判断是否是循环引用对象,具体思路是遍历对象的值是否存在与源的地址相同的情况

function cycle(obj, parent) {
    //表示调用的父级数组
    var parentArr = parent || [obj];
    for (var i in obj) {
        if (typeof obj[i] === "object") {
            //判断是否有循环引用
            parentArr.forEach((pObj) => {
                if (pObj === obj[i]) {
                    obj[i] = "[cycle]"
                }
            });
            cycle(obj[i], [...parentArr, obj[i]])
        }
    }
    return obj;
}

12、跨域,img标签为什么没有跨域问题
浏览器要求,在解析Ajax请求时,要求浏览器的路径与Ajax的请求的路径必须满足三个要求,则满足同源策略,可以访问服务器
协议、域名、端口号都相同才为同源,否则会跨域
解决跨域的方法:

  • CORS;服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源。
  • JSONP; script、img、link、iframe … 这些标签不存在跨域请求的限制,就是利用这个特点解决跨域问题。核心思想:网页通过添加一个
this.x = 9;    // 在浏览器中,this 指向全局的 "window" 对象
var module = {
  x: 81,
  getX: function() { return this.x; }
};
module.getX(); // 81
var retrieveX = module.getX;
retrieveX();
// 返回 9 - 因为函数是在全局作用域中调用的
// 创建一个新函数,把 'this' 绑定到 module 对象
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81

1、call、bind、apply使用目的上都是一样的,都是将一个构造方法当作一个普通方法调用;均传递是一个this域对象;均可传递参数。
2、不同点: call、apply直接调用,bind返回一个新函数需手动调用;apply的参数为数组形式,call、bind为单个参数
手写call:(与apply的不同仅为处理参数时, let args = arguments[1] )

Function.prototype.myCall = function(context) {
  // 判断是否是undefined和null
  if (typeof context === 'undefined' || context === null) {
    context = window
  }
  // call一个参数是其改变指向的对象,后续参数作为实参传递给调用者。所以这里用[...arguments].slice(1)获取到了传递给调用者的函数参数。
  let args = [...arguments].slice(1)  
  context.fn = this  // 将调用的函数设置为参数context的方法
  let result = context.fn(...args)  // 调用函数
  delete context.fn // 移除属性
  return result // 返回结果
}

手写bind:

Function.prototype.myBind = function(context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  let _this = this
  let args = [...arguments].slice(1)
  return function F() {
    // 判断是否被当做构造函数使用
    if (this instanceof F) {
      return _this.apply(this, args.concat([...arguments]))
    }
    return _this.apply(context, args.concat([...arguments]))
  }
}

15、Map
Map 数据结构类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
包含以下属性和操作方法:

  • size 属性;返回 Map 结构的成员总数。
  • Map.prototype.set(key, value)
  • Map.prototype.get(key)
  • Map.prototype.get(key);has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
  • Map.prototype.delete(key);delete方法删除某个键,返回true。如果删除失败,返回false。
  • Map.prototype.clear();clear方法清除所有成员,没有返回值。
    遍历方法:
  • Map.prototype.keys():返回键名的遍历器。
  • Map.prototype.values():返回键值的遍历器。
  • Map.prototype.entries():返回所有成员的遍历器。
  • Map.prototype.forEach():遍历 Map 的所有成员。

17、手写instanceof
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上object instanceof constructor;object为 某个实例对象、constructor 为某个构造函数
instanceof可以用于判断复杂数据类型

function myInstanceOf(object, constructor) {
    if (obj === null || type obj !== 'object' || typeof constructor !== 'function') return false
    let pointer = object._proto_
    while (pointer !== null){
        if (pointer === constructor.prototype) return true
        else pointer = pointer._proto_
    }
}

18、0.1 + 0.2 为什么不等于 0.3
因为在 0.1+0.2 的计算过程中发生了两次精度丢失。第一次是在 0.1 和 0.2 转成双精度二进制浮点数时,由于二进制浮点数的小数位只能存储52位,导致小数点后第53位的数要进行为1则进1为0则舍去的操作,从而造成一次精度丢失。第二次在 0.1 和 0.2 转成二进制浮点数后,二进制浮点数相加的过程中,小数位相加导致小数位多出了一位,又要让第53位的数进行为1则进1为0则舍去的操作,又造成一次精度丢失。最终导致 0.1+0.2 不等于0.3 。

19、js 垃圾回收机制
各大浏览器通常采用的垃圾回收有两种方法:标记清除、引用计数

  • 标记清除。当变量进入执行环境时,将这个变量标记为“进入环境”。当变量离开执行环境时,则将其标记为“离开环境”,就销毁回收内存
  • 引用计数。跟踪记录每个值被引用的次数,当引用次数变成0时,就销毁回收内存

20、=的区别
1、对于 string、number 等基础类型,== 和 === 是有区别的
a)不同类型间比较,== 之比较 “转化成同一类型后的值” 看 “值” 是否相等,=== 如果类型不同,其结果就是不等。
b)同类型比较,直接进行 “值” 比较,两者结果一样。
2、对于 Array,Object 等高级类型,== 和 === 是没有区别的
进行 “指针地址” 比较
3、基础类型与高级类型,== 和 === 是有区别的
a)对于 ,将高级转化为基础类型,进行 “值” 比较
b)因为类型不同,
= 结果为 false

你可能感兴趣的:(js,面试,javascript,es6,前端)