面的不好,很多都知道自己看过,但临场手写就写不对了,手写真耗时间,写着写着,查查错,面试就快结束了,自己承认知识掌握不扎实,仍需努力夯实基础。再次梳理一遍,对自己还是有帮助的。已凉,不过也让我认识到了自己的不足。 自我介绍 怎么学习前端的 你用过vue,知道vue的key是用来做什么的吧 key是为Vue中虚拟DOM标记的唯一id,通过这个key,我们的父节点可以更准确、更快速定位子节点. 讲一讲vue和react的区别 vue双向数据绑定,react单向,需要setState实现medel-view的转换 拓展: 数据绑定:Vue实现了双向的数据绑定,react数据流动是单向的 数据渲染:大规模的数据渲染,react更快 使用场景:React配合Redux架构适合大规模多人协作复杂项目,Vue适合小快的项目 开发风格:react推荐做法jsx + inline style把html和css都写在js了,vue是采用webpack +vue-loader单文件组件格式,html, js, css同一个文件 Vue怎么实现双向绑定的 Vue2.X通过 Object.defineProperty() 来劫持各个属性的setter,getter,新版本通过Proxy劫持 要想深入讲解知识点: Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty() 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue追踪依赖,在属性被访问和修改时通知变化。 vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。 拓展:代码(没写,自己总结面经顺便复习) defineProperty 版本 // 数据const data = { text: 'default'};const input = document.getElementById('input');const span = document.getElementById('span');// 数据劫持Object.defineProperty(data, 'text', { // 数据变化 --> 修改视图 set(newVal) { input.value = newVal; span.innerHTML = newVal; }});// 数据变化 --> 修改视图input.addEventLisener('keyup', function(e) { data.text = e.target.value;}); proxy 版本 // 数据const data = { text: 'default'};const input = document.getElementById('input');const span = document.getElementById('span');// 数据劫持const handler = { set(target, key, value) { target[key] = value; // 数据变化 --> 修改视图 input.value = newVal; span.innerHTML = newVal; return value; }};const proxy = new Proxy(data, handler);// 视图更改 --> 数据变化input.addEventLisener('keyup', function(e) { proxy.text = e.target.value;}); 聊一聊原型链是什么 每个对象都有prototype(原型),当我们访问一个对象的属性时,在找不到的情况再会在其原型上查找是否有这个属性,再找不到会去自己 [__proto__] 关联的 prototype 对象上去找,顺着原型链知道undefined。语言组织太差,有种有货道不出的感觉。 好了,做一道题 Function.prototype.a = () => { console.log(1);}Object.prototype.b = () => { console.log(2);}function A() {}const obj = new A();obj.a() // Errorobj.b() // 2A.a() // 1A.b() // 2 解释了通过函数声明可以同时继承Function和Object原型上的方法,通过new继承只能继承Object的方法 面试官问第一个Error是什么Error,脑抽了说是引用Error(ReferenceError),实际上是TypeErro,打印的是这个结果 Uncaught TypeError: obj.a is not a function 说到继承,你写一下你知道的继承方式 这下充分暴露了自己的不足,知道的六种继承方式,并说了出来,要求手写才发现很多都写错了 function person() { this.kind="person";}person.prototype.eat = function (food) { console.log(this.name+" is eating "+food);}function student() {} 先写了一下继承后上述prototype和proto的关系 student.prototype.__proto__ === person.prototype 原型继承 student.prototype = new person(); 缺点:原型是所有子类实例共享的,改变一个其他也会改变。 该打,我没写完全对 构造继承 function student() { person.call(this);} 缺点:不能继承父类原型,函数在构造函数中,每个子类实例不能共享函数,浪费内存。 这个倒是完全写对了 组合继承 function student() { person.call(this);}student.prototype = new Person(); 缺点:person的构造函数会多执行了一次 哭了,又没有完全写对 原型式继承 student.prototype = Object.create(person.prototype); 又搞错了 写成了 student.prototype = Object.create(person()); 不知道怎么想的,面试官小姐姐提示我知道Object.create怎么一回事,还口述了手写过程 Object.prototype.myCreate(proto) { function f() {}; f.prototype = proto; return new f();} 口述的方法倒是对的 口述之后我发现person()不对劲,又重写了 student.prototype = Object.create(person); 面试官小姐姐温柔地说,没关系,我可能太紧张了 之后是寄生组合继承 function student() { person.call(this);}student.prototype = new person();// 或者student.prototype = Object.create(person.prototype); 我的错误跟上面的一样, 这种继承方法父类原型和子类原型是同一个对象,无法区分子类真正是由谁构造。 最后当然是最完美的寄生组合优化继承 function student() { person.call(this);}student.prototype = new person();// 或者student.prototype = Object.create(person.prototype);student.prototype.constructor = student; 总之错误真多,觉得没脸见人了,手写和口述讲思路难度差距真大 面试官问我知不知道防抖和节流 我回答知道,防抖在定时器定时期间再次触发,关掉旧的定时器,开始新的定时器。将多次执行变为最后一次执行,典型应用,提交按钮的点击事件。 节流在一段时间,只执行一次事件。等到这段之后再根据这段时间内由出发执行,将多次执行变成每隔一段时间执行,典型应用,拖拽事件。 我们用 leading 代表首次是否执行,trailing 代表结束后是否再执行一次 让我手写防抖,第一次手写如下 function debounce(func, wait = 1000) { let timer = null; return function(...args) { if (timer) { timer = null; } timer = setTimeout(func(...args), wait); } } 问我为什么要看到定时器把定时器赋值为null,我说要清空定时器,有没有更好的办法,我想了想说,这是一个引用类型,原先的定时器仍在内存中,可能导致内存泄露(还说错了,说成了本专业学过的频谱泄露),所以应该关闭定时器 function debounce(func, wait = 1000) { let timer = null; return function(...args) { if (timer) { clearTimeout(timer); } timer = setTimeout(func(...args), wait); }} 自己补充再重写一下手写节流吧 function throttle(func, wait = 1000) { let last = 0; return function(...args) { let now = +new Date(); if ((now - last) > wait) { last = now; func(...args); } }} 了解不了解事件循环机制,给我出了这一题 const promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve("success"); console.log("timer1"); }, 1000); console.log("promise1里的内容"); });const promise2 = promise1.then(() => { throw new Error("error!!!");});console.log("promise1", promise1);console.log("promise2", promise2);setTimeout(() => { console.log("timer2"); console.log("promise1", promise1); console.log("promise2", promise2); },2000); 手写输出的顺序 promise1里的内容promise1: Proimse{<pending>}promise2: Proimse{<pending>}timer1Error: error!!!timer2promise1: Proimse{<fulfilled>:'success'}promise2: Proimse{<rejected>:Error'error'} 写完之后讲了讲思路,讲了promise是微任务,setTimeout是宏任务,事件循环机制:执行一个宏任务,执行微任务列表,经过循环再执行宏任务,再执行微任务列表。并提出老版本node循环机制不同把当前时刻宏任务、微任务执行顺序有不同 JS有哪些宏任务、微任务 宏任务setTimeout、setInterval(漏了setImmediate) 补充优先级:主代码块 > setImmediate > MessageChannel > setTimeout / setInterval 微任务包括:优先级:process.nextTick > Promise > MutationObserver 微任务提到了async、await,await后面的看作是promise.then后面的 CSS position有哪些: 只说出来absolute、relative、fixed,不常用的背过忘了,抄一遍菜鸟上的加强记忆 值 描述 absolute 生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。 fixed 生成绝对定位的元素,相对于浏览器窗口进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。 relative 生成相对定位的元素,相对于其正常位置进行定位。因此,"left:20" 会向元素的 LEFT 位置添加 20 像素。 static 默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。 inherit 规定应该从父元素继承 position 属性的值。 提了父相子绝,被问如果父元素没有设置position:relative,子节点设置position:absolute怎么办,我说会一个个找父节点直到找到设置position:relative的,错了,应该是找到position:static以外的第一个父元素。 反问