写点什么

百度前端二面常考手写面试题总结

  • 2022-11-02
    浙江
  • 本文字数:11259 字

    阅读完需:约 37 分钟

将 VirtualDom 转化为真实 DOM 结构

这是当前 SPA 应用的核心概念之一


// vnode结构:// {//   tag,//   attrs,//   children,// }
//Virtual DOM => DOMfunction render(vnode, container) { container.appendChild(_render(vnode));}function _render(vnode) { // 如果是数字类型转化为字符串 if (typeof vnode === 'number') { vnode = String(vnode); } // 字符串类型直接就是文本节点 if (typeof vnode === 'string') { return document.createTextNode(vnode); } // 普通DOM const dom = document.createElement(vnode.tag); if (vnode.attrs) { // 遍历属性 Object.keys(vnode.attrs).forEach(key => { const value = vnode.attrs[key]; dom.setAttribute(key, value); }) } // 子数组进行递归操作 vnode.children.forEach(child => render(child, dom)); return dom;}
复制代码

实现 every 方法

Array.prototype.myEvery=function(callback, context = window){    var len=this.length,        flag=true,        i = 0;
for(;i < len; i++){ if(!callback.apply(context,[this[i], i , this])){ flag=false; break; } } return flag; }

// var obj = {num: 1} // var aa=arr.myEvery(function(v,index,arr){ // return v.num>=12; // },obj) // console.log(aa)
复制代码

实现 Array.isArray 方法

Array.myIsArray = function(o) {  return Object.prototype.toString.call(Object(o)) === '[object Array]';};
console.log(Array.myIsArray([])); // true
复制代码

实现数组扁平化 flat 方法

题目描述: 实现一个方法使多维数组变成一维数组


let ary = [1, [2, [3, [4, 5]]], 6];let str = JSON.stringify(ary);
复制代码


第 0 种处理:直接的调用


arr_flat = arr.flat(Infinity);
复制代码


第一种处理


ary = str.replace(/(\[|\])/g, '').split(',');
复制代码


第二种处理


str = str.replace(/(\[\]))/g, '');str = '[' + str + ']';ary = JSON.parse(str);
复制代码


第三种处理:递归处理


let result = [];let fn = function(ary) {  for(let i = 0; i < ary.length; i++) }{    let item = ary[i];    if (Array.isArray(ary[i])){      fn(item);    } else {      result.push(item);    }  }}
复制代码


第四种处理:用 reduce 实现数组的 flat 方法


function flatten(ary) {    return ary.reduce((pre, cur) => {        return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);    }, []);}let ary = [1, 2, [3, 4], [5, [6, 7]]]console.log(flatten(ary))
复制代码


第五种处理:能用迭代的思路去实现


function flatten(arr) {  if (!arr.length) return;  while (arr.some((item) => Array.isArray(item))) {    arr = [].concat(...arr);  }  return arr;}// console.log(flatten([1, 2, [1, [2, 3, [4, 5, [6]]]]]));
复制代码


第六种处理:扩展运算符


while (ary.some(Array.isArray)) {  ary = [].concat(...ary);}
复制代码

实现节流函数(throttle)

节流函数原理:指频繁触发事件时,只会在指定的时间段内执行事件回调,即触发事件间隔大于等于指定的时间才会执行回调函数。总结起来就是: 事件,按照一段时间的间隔来进行触发



像 dom 的拖拽,如果用消抖的话,就会出现卡顿的感觉,因为只在停止的时候执行了一次,这个时候就应该用节流,在一定时间内多次执行,会流畅很多


手写简版


使用时间戳的节流函数会在第一次触发事件时立即执行,以后每过 wait 秒之后才执行一次,并且最后一次触发事件不会被执行


时间戳方式:


// func是用户传入需要防抖的函数// wait是等待时间const throttle = (func, wait = 50) => {  // 上一次执行该函数的时间  let lastTime = 0  return function(...args) {    // 当前时间    let now = +new Date()    // 将当前时间和上一次执行函数时间对比    // 如果差值大于设置的等待时间就执行函数    if (now - lastTime > wait) {      lastTime = now      func.apply(this, args)    }  }}
setInterval( throttle(() => { console.log(1) }, 500), 1)
复制代码


定时器方式:


使用定时器的节流函数在第一次触发时不会执行,而是在 delay 秒之后才执行,当最后一次停止触发后,还会再执行一次函数


function throttle(func, delay){  var timer = null;  returnfunction(){    var context = this;    var args = arguments;    if(!timer){      timer = setTimeout(function(){        func.apply(context, args);        timer = null;      },delay);    }  }}
复制代码


适用场景:


  • DOM 元素的拖拽功能实现(mousemove

  • 搜索联想(keyup

  • 计算鼠标移动的距离(mousemove

  • Canvas 模拟画板功能(mousemove

  • 监听滚动事件判断是否到页面底部自动加载更多

  • 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动

  • 缩放场景:监控浏览器resize

  • 动画场景:避免短时间内多次触发动画引起性能问题


总结


  • 函数防抖 :将几次操作合并为一次操作进行。原理是维护一个计时器,规定在 delay 时间后触发函数,但是在 delay 时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。

  • 函数节流 :使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。

实现模板字符串解析功能

let template = '我是{{name}},年龄{{age}},性别{{sex}}';let data = {  name: '姓名',  age: 18}render(template, data); // 我是姓名,年龄18,性别undefined
复制代码


function render(template, data) {  const reg = /\{\{(\w+)\}\}/; // 模板字符串正则  if (reg.test(template)) { // 判断模板里是否有模板字符串    const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段    template = template.replace(reg, data[name]); // 将第一个模板字符串渲染    return render(template, data); // 递归的渲染并返回渲染后的结构  }  return template; // 如果模板没有模板字符串直接返回}
复制代码

实现一下 hash 路由

基础的html代码:


<html>  <style>    html, body {      margin: 0;      height: 100%;    }    ul {      list-style: none;      margin: 0;      padding: 0;      display: flex;      justify-content: center;    }    .box {      width: 100%;      height: 100%;      background-color: red;    }  </style>  <body>  <ul>    <li>      <a href="#red">红色</a>    </li>    <li>      <a href="#green">绿色</a>    </li>    <li>      <a href="#purple">紫色</a>    </li>  </ul>  </body></html>
复制代码


简单实现:


<script>  const box = document.getElementsByClassName('box')[0];  const hash = location.hash  window.onhashchange = function (e) {    const color = hash.slice(1)    box.style.background = color  }</script>
复制代码


封装成一个 class:


<script>  const box = document.getElementsByClassName('box')[0];  const hash = location.hash  class HashRouter {    constructor (hashStr, cb) {      this.hashStr = hashStr      this.cb = cb      this.watchHash()      this.watch = this.watchHash.bind(this)      window.addEventListener('hashchange', this.watch)    }    watchHash () {      let hash = window.location.hash.slice(1)      this.hashStr = hash      this.cb(hash)    }  }  new HashRouter('red', (color) => {    box.style.background = color  })</script>
复制代码


参考:前端手写面试题详细解答

实现 redux-thunk

redux-thunk 可以利用 redux 中间件让 redux 支持异步的 action


// 如果 action 是个函数,就调用这个函数// 如果 action 不是函数,就传给下一个中间件// 发现 action 是函数就调用const thunk = ({ dispatch, getState }) => (next) => (action) => {  if (typeof action === 'function') {    return action(dispatch, getState);  }
return next(action);};export default thunk
复制代码

模拟 new

new 操作符做了这些事:


  • 它创建了一个全新的对象

  • 它会被执行[[Prototype]](也就是__proto__)链接

  • 它使 this 指向新创建的对象

  • 通过 new 创建的每个对象将最终被[[Prototype]]链接到这个函数的 prototype 对象上

  • 如果函数没有返回对象类型 Object(包含 Functoin, Array, Date, RegExg, Error),那么 new 表达式中的函数调用将返回该对象引用


// objectFactory(name, 'cxk', '18')function objectFactory() {  const obj = new Object();  const Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
const ret = Constructor.apply(obj, arguments);
return typeof ret === "object" ? ret : obj;}
复制代码

实现 redux 中间件

简单实现


function createStore(reducer) {  let currentState  let listeners = []
function getState() { return currentState }
function dispatch(action) { currentState = reducer(currentState, action) listeners.map(listener => { listener() }) return action }
function subscribe(cb) { listeners.push(cb) return () => {} }
dispatch({type: 'ZZZZZZZZZZ'})
return { getState, dispatch, subscribe }}
// 应用实例如下:function reducer(state = 0, action) { switch (action.type) { case 'ADD': return state + 1 case 'MINUS': return state - 1 default: return state }}
const store = createStore(reducer)
console.log(store);store.subscribe(() => { console.log('change');})console.log(store.getState());console.log(store.dispatch({type: 'ADD'}));console.log(store.getState());
复制代码


2. 迷你版


export const createStore = (reducer,enhancer)=>{    if(enhancer) {        return enhancer(createStore)(reducer)    }    let currentState = {}    let currentListeners = []
const getState = ()=>currentState const subscribe = (listener)=>{ currentListeners.push(listener) } const dispatch = action=>{ currentState = reducer(currentState, action) currentListeners.forEach(v=>v()) return action } dispatch({type:'@@INIT'}) return {getState,subscribe,dispatch}}
//中间件实现export applyMiddleWare(...middlewares){ return createStore=>...args=>{ const store = createStore(...args) let dispatch = store.dispatch
const midApi = { getState:store.getState, dispatch:...args=>dispatch(...args) } const middlewaresChain = middlewares.map(middleware=>middleware(midApi)) dispatch = compose(...middlewaresChain)(store.dispatch) return { ...store, dispatch } }
// fn1(fn2(fn3())) 把函数嵌套依次调用export function compose(...funcs){ if(funcs.length===0){ return arg=>arg } if(funs.length===1){ return funs[0] } return funcs.reduce((ret,item)=>(...args)=>ret(item(...args)))}

//bindActionCreator实现
function bindActionCreator(creator,dispatch){ return ...args=>dispatch(creator(...args))}function bindActionCreators(creators,didpatch){ //let bound = {} //Object.keys(creators).forEach(v=>{ // let creator = creator[v] // bound[v] = bindActionCreator(creator,dispatch) //}) //return bound
return Object.keys(creators).reduce((ret,item)=>{ ret[item] = bindActionCreator(creators[item],dispatch) return ret },{})}
复制代码

类数组转化为数组

类数组是具有 length 属性,但不具有数组原型上的方法。常见的类数组有 arguments、DOM 操作方法返回的结果。

方法一:Array.from
Array.from(document.querySelectorAll('div'))
复制代码
方法二:Array.prototype.slice.call()
Array.prototype.slice.call(document.querySelectorAll('div'))
复制代码
方法三:扩展运算符
[...document.querySelectorAll('div')]
复制代码
方法四:利用 concat
Array.prototype.concat.apply([], document.querySelectorAll('div'));
复制代码

实现 LRU 淘汰算法

LRU 缓存算法是一个非常经典的算法,在很多面试中经常问道,不仅仅包括前端面试


LRU 英文全称是 Least Recently Used,英译过来就是” 最近最少使用 “的意思。LRU 是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰


通俗的解释:


假如我们有一块内存,专门用来缓存我们最近发访问的网页,访问一个新网页,我们就会往内存中添加一个网页地址,随着网页的不断增加,内存存满了,这个时候我们就需要考虑删除一些网页了。这个时候我们找到内存中最早访问的那个网页地址,然后把它删掉。这一整个过程就可以称之为 LRU 算法



上图就很好的解释了 LRU 算法在干嘛了,其实非常简单,无非就是我们往内存里面添加或者删除元素的时候,遵循最近最少使用原则


使用场景


LRU 算法使用的场景非常多,这里简单举几个例子即可:


  • 我们操作系统底层的内存管理,其中就包括有 LRU 算法

  • 我们常见的缓存服务,比如 redis 等等

  • 比如浏览器的最近浏览记录存储

  • vue中的keep-alive组件使用了LRU算法


梳理实现 LRU 思路


  • 特点分析:

  • 我们需要一块有限的存储空间,因为无限的化就没必要使用LRU算发删除数据了。

  • 我们这块存储空间里面存储的数据需要是有序的,因为我们必须要顺序来删除数据,所以可以考虑使用 ArrayMap 数据结构来存储,不能使用 Object,因为它是无序的。

  • 我们能够删除或者添加以及获取到这块存储空间中的指定数据。

  • 存储空间存满之后,在添加数据时,会自动删除时间最久远的那条数据。

  • 实现需求:

  • 实现一个 LRUCache 类型,用来充当存储空间

  • 采用 Map 数据结构存储数据,因为它的存取时间复杂度为 O(1),数组为 O(n)

  • 实现 getset 方法,用来获取和添加数据

  • 我们的存储空间有长度限制,所以无需提供删除方法,存储满之后,自动删除最久远的那条数据

  • 当使用 get 获取数据后,该条数据需要更新到最前面


具体实现


class LRUCache {  constructor(length) {    this.length = length; // 存储长度    this.data = new Map(); // 存储数据  }  // 存储数据,通过键值对的方式  set(key, value) {    const data = this.data;    if (data.has(key)) {      data.delete(key)    }
data.set(key, value);
// 如果超出了容量,则需要删除最久的数据 if (data.size > this.length) { const delKey = data.keys().next().value; data.delete(delKey); } } // 获取数据 get(key) { const data = this.data; // 未找到 if (!data.has(key)) { return null; } const value = data.get(key); // 获取元素 data.delete(key); // 删除元素 data.set(key, value); // 重新插入元素
return value // 返回获取的值 }}var lruCache = new LRUCache(5);
复制代码


  • set 方法:往 map 里面添加新数据,如果添加的数据存在了,则先删除该条数据,然后再添加。如果添加数据后超长了,则需要删除最久远的一条数据。data.keys().next().value 便是获取最后一条数据的意思。

  • get 方法:首先从 map 对象中拿出该条数据,然后删除该条数据,最后再重新插入该条数据,确保将该条数据移动到最前面


// 测试
// 存储数据 set:
lruCache.set('name', 'test');lruCache.set('age', 10);lruCache.set('sex', '男');lruCache.set('height', 180);lruCache.set('weight', '120');console.log(lruCache);
复制代码



继续插入数据,此时会超长,代码如下:


lruCache.set('grade', '100');console.log(lruCache);
复制代码



此时我们发现存储时间最久的 name 已经被移除了,新插入的数据变为了最前面的一个。


我们使用 get 获取数据,代码如下:



我们发现此时 sex 字段已经跑到最前面去了


总结


LRU 算法其实逻辑非常的简单,明白了原理之后实现起来非常的简单。最主要的是我们需要使用什么数据结构来存储数据,因为 map 的存取非常快,所以我们采用了它,当然数组其实也可以实现的。还有一些小伙伴使用链表来实现 LRU,这当然也是可以的。

实现类的继承

实现类的继承-简版

类的继承在几年前是重点内容,有 n 种继承方式各有优劣,es6 普及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深入理解的去看红宝书即可,我们目前只实现一种最理想的继承方式。


// 寄生组合继承function Parent(name) {  this.name = name}Parent.prototype.say = function() {  console.log(this.name + ` say`);}Parent.prototype.play = function() {  console.log(this.name + ` play`);}
function Child(name, parent) { // 将父类的构造函数绑定在子类上 Parent.call(this, parent) this.name = name}
/** 1. 这一步不用Child.prototype = Parent.prototype的原因是怕共享内存,修改父类原型对象就会影响子类 2. 不用Child.prototype = new Parent()的原因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性3. Object.create是创建了父类原型的副本,与父类原型完全隔离*/Child.prototype = Object.create(Parent.prototype);Child.prototype.say = function() { console.log(this.name + ` say`);}
// 注意记得把子类的构造指向子类本身Child.prototype.constructor = Child;
复制代码


// 测试var parent = new Parent('parent');parent.say() 
var child = new Child('child');child.say() child.play(); // 继承父类的方法
复制代码

ES5 实现继承-详细

第一种方式是借助 call 实现继承


function Parent1(){    this.name = 'parent1';}function Child1(){    Parent1.call(this);    this.type = 'child1'    }console.log(new Child1);
复制代码


这样写的时候子类虽然能够拿到父类的属性值,但是问题是父类中一旦存在方法那么子类无法继承。那么引出下面的方法


第二种方式借助原型链实现继承:


function Parent2() {    this.name = 'parent2';    this.play = [1, 2, 3]  }  function Child2() {    this.type = 'child2';  }  Child2.prototype = new Parent2();
console.log(new Child2());
复制代码


看似没有问题,父类的方法和属性都能够访问,但实际上有一个潜在的不足。举个例子:


var s1 = new Child2();  var s2 = new Child2();  s1.play.push(4);  console.log(s1.play, s2.play); // [1,2,3,4] [1,2,3,4]
复制代码


明明我只改变了 s1 的 play 属性,为什么 s2 也跟着变了呢?很简单,因为两个实例使用的是同一个原型对象


第三种方式:将前两种组合:


function Parent3 () {    this.name = 'parent3';    this.play = [1, 2, 3];  }  function Child3() {    Parent3.call(this);    this.type = 'child3';  }  Child3.prototype = new Parent3();  var s3 = new Child3();  var s4 = new Child3();  s3.play.push(4);  console.log(s3.play, s4.play); // [1,2,3,4] [1,2,3]
复制代码


之前的问题都得以解决。但是这里又徒增了一个新问题,那就是 Parent3 的构造函数会多执行了一次(Child3.prototype = new Parent3();)。这是我们不愿看到的。那么如何解决这个问题?


第四种方式: 组合继承的优化 1


function Parent4 () {    this.name = 'parent4';    this.play = [1, 2, 3];  }  function Child4() {    Parent4.call(this);    this.type = 'child4';  }  Child4.prototype = Parent4.prototype;
复制代码


这里让将父类原型对象直接给到子类,父类构造函数只执行一次,而且父类属性和方法均能访问,但是我们来测试一下


var s3 = new Child4();  var s4 = new Child4();  console.log(s3)
复制代码


子类实例的构造函数是 Parent4,显然这是不对的,应该是 Child4。


第五种方式(最推荐使用):优化 2


function Parent5 () {    this.name = 'parent5';    this.play = [1, 2, 3];  }  function Child5() {    Parent5.call(this);    this.type = 'child5';  }  Child5.prototype = Object.create(Parent5.prototype);  Child5.prototype.constructor = Child5;
复制代码


这是最推荐的一种方式,接近完美的继承。

实现 Object.freeze

Object.freeze冻结一个对象,让其不能再添加/删除属性,也不能修改该对象已有属性的可枚举性、可配置可写性,也不能修改已有属性的值和它的原型属性,最后返回一个和传入参数相同的对象


function myFreeze(obj){  // 判断参数是否为Object类型,如果是就封闭对象,循环遍历对象。去掉原型属性,将其writable特性设置为false  if(obj instanceof Object){    Object.seal(obj);  // 封闭对象    for(let key in obj){      if(obj.hasOwnProperty(key)){        Object.defineProperty(obj,key,{          writable:false   // 设置只读        })        // 如果属性值依然为对象,要通过递归来进行进一步的冻结        myFreeze(obj[key]);        }    }  }}
复制代码

实现迭代器生成函数

我们说迭代器对象全凭迭代器生成函数帮我们生成。在ES6中,实现一个迭代器生成函数并不是什么难事儿,因为 ES6 早帮我们考虑好了全套的解决方案,内置了贴心的 生成器Generator)供我们使用:


// 编写一个迭代器生成函数function *iteratorGenerator() {    yield '1号选手'    yield '2号选手'    yield '3号选手'}
const iterator = iteratorGenerator()
iterator.next()iterator.next()iterator.next()
复制代码


丢进控制台,不负众望:



写一个生成器函数并没有什么难度,但在面试的过程中,面试官往往对生成器这种语法糖背后的实现逻辑更感兴趣。下面我们要做的,不仅仅是写一个迭代器对象,而是用ES5去写一个能够生成迭代器对象的迭代器生成函数(解析在注释里):


// 定义生成器函数,入参是任意集合function iteratorGenerator(list) {    // idx记录当前访问的索引    var idx = 0    // len记录传入集合的长度    var len = list.length    return {        // 自定义next方法        next: function() {            // 如果索引还没有超出集合长度,done为false            var done = idx >= len            // 如果done为false,则可以继续取值            var value = !done ? list[idx++] : undefined
// 将当前值与遍历是否完毕(done)返回 return { done: done, value: value } } }}
var iterator = iteratorGenerator(['1号选手', '2号选手', '3号选手'])iterator.next()iterator.next()iterator.next()
复制代码


此处为了记录每次遍历的位置,我们实现了一个闭包,借助自由变量来做我们的迭代过程中的“游标”。


运行一下我们自定义的迭代器,结果符合预期:


实现非负大整数相加

JavaScript 对数值有范围的限制,限制如下:


Number.MAX_VALUE // 1.7976931348623157e+308Number.MAX_SAFE_INTEGER // 9007199254740991Number.MIN_VALUE // 5e-324Number.MIN_SAFE_INTEGER // -9007199254740991
复制代码


如果想要对一个超大的整数(> Number.MAX_SAFE_INTEGER)进行加法运算,但是又想输出一般形式,那么使用 + 是无法达到的,一旦数字超过 Number.MAX_SAFE_INTEGER 数字会被立即转换为科学计数法,并且数字精度相比以前将会有误差。


实现一个算法进行大数的相加:


function sumBigNumber(a, b) {  let res = '';  let temp = 0;
a = a.split(''); b = b.split('');
while (a.length || b.length || temp) { temp += ~~a.pop() + ~~b.pop(); res = (temp % 10) + res; temp = temp > 9 } return res.replace(/^0+/, '');}
复制代码


其主要的思路如下:


  • 首先用字符串的方式来保存大数,这样数字在数学表示上就不会发生变化

  • 初始化 res,temp 来保存中间的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算

  • 将两个数组的对应的位进行相加,两个数相加的结果可能大于 10,所以可能要仅为,对 10 进行取余操作,将结果保存在当前位

  • 判断当前位是否大于 9,也就是是否会进位,若是则将 temp 赋值为 true,因为在加法运算中,true 会自动隐式转化为 1,以便于下一次相加

  • 重复上述操作,直至计算结束

实现 prototype 继承

所谓的原型链继承就是让新实例的原型等于父类的实例:


//父方法function SupperFunction(flag1){    this.flag1 = flag1;}
//子方法function SubFunction(flag2){ this.flag2 = flag2;}
//父实例var superInstance = new SupperFunction(true);
//子继承父SubFunction.prototype = superInstance;
//子实例var subInstance = new SubFunction(false);//子调用自己和父的属性subInstance.flag1; // truesubInstance.flag2; // false
复制代码

实现双向数据绑定

let obj = {}let input = document.getElementById('input')let span = document.getElementById('span')// 数据劫持Object.defineProperty(obj, 'text', {  configurable: true,  enumerable: true,  get() {    console.log('获取数据了')  },  set(newVal) {    console.log('数据更新了')    input.value = newVal    span.innerHTML = newVal  }})// 输入监听input.addEventListener('keyup', function(e) {  obj.text = e.target.value})
复制代码

使用 ES5 和 ES6 求函数参数的和

ES5:


function sum() {    let sum = 0    Array.prototype.forEach.call(arguments, function(item) {        sum += item * 1    })    return sum}
复制代码


ES6:


function sum(...nums) {    let sum = 0    nums.forEach(function(item) {        sum += item * 1    })    return sum}
复制代码

实现观察者模式

观察者模式(基于发布订阅模式) 有观察者,也有被观察者


观察者需要放到被观察者中,被观察者的状态变化需要通知观察者 我变化了 内部也是基于发布订阅模式,收集观察者,状态变化后要主动通知观察者


class Subject { // 被观察者 学生  constructor(name) {    this.state = 'happy'    this.observers = []; // 存储所有的观察者  }  // 收集所有的观察者  attach(o){ // Subject. prototype. attch    this.observers.push(o)  }  // 更新被观察者 状态的方法  setState(newState) {    this.state = newState; // 更新状态    // this 指被观察者 学生    this.observers.forEach(o => o.update(this)) // 通知观察者 更新它们的状态  }}
class Observer{ // 观察者 父母和老师 constructor(name) { this.name = name } update(student) { console.log('当前' + this.name + '被通知了', '当前学生的状态是' + student.state) }}
let student = new Subject('学生');
let parent = new Observer('父母'); let teacher = new Observer('老师');
// 被观察者存储观察者的前提,需要先接纳观察者student. attach(parent); student. attach(teacher); student. setState('被欺负了');
复制代码


用户头像

还未添加个人签名 2022-07-31 加入

还未添加个人简介

评论

发布
暂无评论
百度前端二面常考手写面试题总结_JavaScript_helloworld1024fd_InfoQ写作社区