面试进阶JS two

198 阅读10分钟

JS基础

new的原理

  • new做了哪些事
  • new返回不同的类型会有什么表现
  • 手写new的实现过程

执行一个构造函数,返回一个实例对象

  • 创建一个新对象
  • 使得新对象的__proto__ 指向构造函数的prototype
  • 执行构造函数,通过call apply 改变this的指向
  • 而后判断确保返回的是一个对象。否则返回新对象,而不是new步骤生成的this对象

其中有两种分支情况

不使用new的情况 构造函数中包含return 语句的

new关键词执行之后总会返回一个对象

手撕

function create(fn,...args){
  var obj = Object.create(fn.prototype)
  var res = fn.apply(fn,args)
  return typeof res === 'object' ? res : obj
}


原型/原型链

__proto__和prototype关系:__proto__和constructor是对象独有的 ,prototype属性是函数独有的

当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。

  • 原型(prototype):用于实现对象的属性继承。每个JavaScript对象中都包含一个__proto__(非标准)的属性指向它爹(该对象的原型),可obj.__proto__进行访问。

  • 构造函数: 通过new新建一个对象的函数

  • 实例: 通过构造函数和new创建出来的对象,即为实例 实例通过__proto__ 指向原型,通过constructor指向构造函数

原型链

原型链是由原型对象组成,每个对象都有 proto 属性,指向了创建该对象的构造函数的原型,proto 将对象连接起来组成了原型链。是一个用来实现继承和共享属性的有限的对象链

属性查找机制: 若这一层查找不到该属性时,则沿着原型链向上查找

属性修改机制: 只会修改实例对象本身的属性,如果不存在,则进行添加该属性,如果需要修改原型的属性时,则可以用: b.prototype.x = 2;但是这样会造成所有继承于该对象的实例的属性发生改变。

每个对象都有 proto 属性,指向了创建该对象的构造函数的原型。其实这个属性指向了 [[prototype]],但是 [[prototype]]是内部属性,我们并不能访问到,所以使用 _proto_来访问。

继承

class为语法糖 ,JS中并不存在类

Person instanceof Function 

需要记住的实际上就两个把

  1. class继承
class Parent {
  constructor(value) {
    this.val = value
  }
  getValue() {
    console.log(this.val)
  }
}
class Child extends Parent {
  constructor(value) {
    super(value)
    this.val = value
  }
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true

class 继承的核心就是 extends表明继承自哪个父类。并且在constructor构造目录中必须调用super()

相当于先借用父类完成子类this的构造,再向子类中添加this的相关属性

  1. 组合继承的最推荐姿势
function Parent5(){
  this.name = 'name'
  this.way = 'father'
}
function Child5(){
  Parent5.call(this)
  this.age = '16'
}

Child5.prototype = Object.create(Parent5.prototype)
Child5.prototype.constructor = Child5

继承

事件机制

  • 事件捕获阶段 有外向内 window document body
  • 处于目标阶段
  • 事件冒泡阶段 由内向外

W3C的标准是先捕获再冒泡, addEventListener的第三个参数决定把事件注册在捕获(true)还是冒泡(false)

  • event.preventDefault():取消事件对象的默认动作以及继续传播。 event.stopPropagation()/ event.cancelBubble = true:阻止事件冒泡。

重点:

  • 通常我们使用 addEventListener 注册事件,该函数的第三个参数可以是布尔值,也可以是对象。对于布尔值 useCapture 参数来说,该参数默认值为 false。useCapture 决定了注册的事件是捕获事件还是冒泡事件
  • 一般来说,我们只希望事件只触发在目标上,这时候可以使用 stopPropagation 来阻止事件的进一步传播。通常我们认为 stopPropagation 是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件。stopImmediatePropagation 同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件

Iterator 迭代器

Iterator(迭代器)是一种接口,也可以说是一种规范。为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。

let arr = [{num:1},2,3]
let it = arr[Symbol.iterator]() // 获取数组中的迭代器
console.log(it.next())  // { value: Object { num: 1 }, done: false }
console.log(it.next())  // { value: 2, done: false }
console.log(it.next())  // { value: 3, done: false }
console.log(it.next())  /

手动给对象布置遍历

let obj = {
  id: '123',
  name: '张三',
  age: 18,
  gender: '男',
  hobbie: '睡觉'
}

obj[Symbol.iterator] = function(){
  let keyarr = Object.keys(obj)
  let index = 0
  return {
    next(){
      return index < keyarr.length ? {
        value: {
          key: keyarr[index],
          val: obj[keyarr[index++]]
        }
      } : {
        done: true
      }
    }
  }
}

for (let key of obj) {
  console.log(key)
}

Promise 标记待完善 重点 async对象返回的是

这里你谈 promise的时候,除了将他解决的痛点以及常用的 API 之外,最好进行拓展把 eventloop 带进来好好讲一下,microtask(微任务)、macrotask(任务) 的执行顺序,如果看过 promise 源码,最好可以谈一谈 原生 Promise 是如何实现的。Promise 的关键点在于callback 的两个参数,一个是 resovle,一个是 reject。还有就是 Promise 的链式调用(Promise.then(),每一个 then 都是一个责任人)

核心大概是源码,以及promise语句是怎么使用的

这个地方讲的不太清楚

Generator

Generator 是 ES6中新增的语法,和 Promise 一样,都可以用来异步编程。Generator函数可以说是Iterator接口的具体实现方式。Generator 最大的特点就是可以控制函数的执行。

应该会算。看题写答案。因为async await 是这个内容的语法糖。写法更加精简

我也不是太明白给两个例子自己揣摩吧

function *foo(x) {
  let y = yield (x + 1)
  let z = yield (y / 3)
  return (x + y + z)
}
let it = foo(5)
console.log(it.next())   // => {value: 6, done: false}
console.log(it.next(12)) // => {value: 8, done: false}
console.log(it.next(13)) // =>

// { value: 6, done: false }
// { value: 4, done: false }
// { value: 30, done: true }
function *foo(x) {
  let y = 2*(yield (x + 1))
  let z = yield (y / 3)
  return (x + y + z)
}
let it = foo(5)
console.log(it.next())   // => {value: 6, done: false}
console.log(it.next(12)) // => {value: 8, done: false}
console.log(it.next(13)) // =>

// { value: 6, done: false }
// { value: 8, done: false }
// { value: 42, done: true }

async/await

注意宏任务与微任务的关系

事件循环

link 之前理解的有些问题

宏任务指的是一些特定的代码段,图里面有

  • 默认代码从上到下执行,执行环境通过script来执行(宏任务)
  • 在代码执行过程中,调用定时器 promise click事件...不会立即执行,需要等待当前代码全部执行完毕
  • 给异步方法划分队列,分别存放到微任务(立即存放)和宏任务(时间到了或事情发生了才存放)到队列中
  • script执行完毕后,会清空所有的微任务
  • 微任务执行完毕后,会渲染页面(不是每次都调用) 再去宏任务队列中看有没有到达时间的,拿出来其中一个执行
  • 执行完毕后,按照上述步骤不停的循环

垃圾回收

回收主要用到 标记清楚 , 引用计数

v8 的垃圾回收机制基于分代回收机制,这个机制又基于世代假说,这个假说有两个特点,一是新生的对象容易早死,另一个是不死的对象会活得更久。基于这个假说,v8 引擎将内存分为了新生代和老生代。

深浅拷贝

浅拷贝出现的情况,引用类型的数据。因为保存的是数据的地址,所以复制时出现浅拷贝

  1. object.assign
let target = {};
let source = { a: { b: 1 } };
Object.assign(target, source);
console.log(target); // { a: { b: 1 } };
  1. 扩展运算符方式

  2. concat 对待数组好像是深拷贝

  3. slice 在原数组上更改, 好像是浅拷贝

节流与防抖

  • 防抖为避免短时多次点击向后端发送多次请求
  • 函数节流为单位事件内重复多次的事件。只有一次能够生效

代码Github 库内都含有

Proxy代理

var f = new Proxy(target,handler)

var f = new Proxy(target,{
    get: function(target,key){
        
    },
    set: function(target,key,value){
        
    }
})

作用:

  • 拦截并监视外部对象对对象的访问
  • 在进行复杂的请求前对操作进行校验或者管理或预处理

Ajax

var xhr = window.XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');// 兼容IE6及以下版本
//2:配置 Ajax请求地址
xhr.open('get','index.xml',true);
//3:发送请求
xhr.send(null); // 严谨写法
//4:监听请求,接受响应
xhr.onreadysatechange=function(){
     if(xhr.readySate==4&&xhr.status==200 || xhr.status==304 )
          console.log(xhr.responsetXML)
}

数组

改变自身的方法

基于 ES6,会改变自身值的方法一共有 9 个,分别为 pop、push、reverse、shift、sort、splice、unshift,以及两个 ES6 新增的方法 copyWithin 和 fill

  • pop,push 从右边添加删除
  • shift unshift shift从左边删除
  • sort(func) 里面其实是一个函数,默认的是左小右大
<!-- 倒叙 -->
var points = [40,100,1,5,25,10];
points.sort(function(a,b){return b-a});
  • splice 修改原数组 添加或者删除元素

    • array.splice(index,howmany,item1,.....,itemX)
    • index 为下标开始位置 howmany 为操作数目
  • slice 不修改原数组

  • array.slice(start, end)

  • reverse

  • fill array.fill(value, start, end)

  • copyWithin

    • array.copyWithin(target, start, end)
    • copyWithin() 方法用于从数组的指定位置拷贝元素到数组的另一个指定位置中。

ES7

  • concat join slice toString toLocaleString、indexOf、lastIndexOf、未形成标准的 toSource,以及 ES7 新增的方法 includes
  • concat
    • arr.concat()
  • join
    • 数组转字符串
  • toString
    • 直译 讲数组转化为字符串 以,相分割
  • toLocaleString()
  • indexof
  • string.indexOf(searchvalue,start) 数组也适用
  • includes
  • 是否包含返回 true false

遍历的方法

forEach、every、some、filter、map、reduce、reduceRight,以及 ES6 新增的方法 entries、find、findIndex、keys、values

forEach

  • array.forEach(function(currentValue, index, arr), thisValue)

every

  • array.every(function(currentValue,index,arr), thisValue)
  • every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。
  • 有一个返回false则结束

some

  • some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。

filter

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

map

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

reduce

  • 就是之前能够处理累加的东西
  • array.reduce(function(total, currentValue, currentIndex, arr), initialValue)

reduceRight

entries

var array = ["a", "b", "c"];
var iterator = array.entries();
console.log(iterator.next().value); // [0, "a"]
console.log(iterator.next().value); // [1, "b"]
console.log(iterator.next().value); // [2, "c"]
console.log(iterator.next().value); // undefined, 迭代器处于数组末尾时, 再迭代就会返回undefined
/

find

  • find() 方法返回通过测试(函数内判断)的数组的第一个元素的值。
  • array.find(function(currentValue, index, arr),thisValue)

findIndex

  • findIndex() 方法返回通过测试(函数内判断)的数组的第一个元素的下标。

keys() 方法用于从数组创建一个包含数组键的可迭代对象。

values() 值

理解JS类数组

函数里面的参数对象 arguments; 用 getElementsByTagName/ClassName/Name 获得的 HTMLCollection 用 querySelector 获得的 NodeList

  • arguments 是函数中传递的参数值的集合。因为它含有length属性,但它毕竟不是数组。没有数组中的内置方法,如forEach,reduce,filter和map
Array.prototype.slice.call(arguments);
Object.prototype.toString.call(arguments)

HTMLCollection

HTMLCollection 简单来说是 HTML DOM 对象的一个接口,这个接口包含了获取到的 DOM 元素集合,返回的类型是类数组对象,如果用 typeof 来判断的话,它返回的是 'object'。它是及时更新的,当文档中的 DOM 变化时,它也会随之变化。

NodeList

类数组对象,可以通过下标进行遍历,传递参数使用 img-repo.poetries.top/images/2021… callee属性指向自己

类数组转化为数组的方法

function sum(a, b) {
  let args = Array.prototype.slice.call(arguments);
 // let args = [].slice.call(arguments); // 这样写也是一样效果
  console.log(args.reduce((sum, cur) => sum + cur));
}
Array.prototype.slice.call(arguments);
Array.prototype.concat.call([],arguments);

// ES6 解构赋值

function sum(a, b) {
  let args = Array.from(arguments);
  console.log(args.reduce((sum, cur) => sum + cur));
}
sum(1, 2);    // 3
function sum(a, b) {
  let args = [...arguments];
  console.log(args.reduce((sum, cur) => sum + cur));
}
sum(1, 2);    // 3
function sum(...args) {
  console.log(args.reduce((sum, cur) => sum + cur));
}
sum(1, 2);    // 

数组扁平化

  • 递归
  • reduce
  • ...
  • flat arr.flat([depth]) 默认为1 depth表示可展开数组的深度。 Infinity 无限深度

通过这个在线网站 regexper.com/ 正则分析成容易理解的可视化的逻辑脑图