基础
个人感觉基础是比较难回答好的,像原型 闭包 this这些概念因为js的语言设计问题会有些绕,加上一开始是培训的java开发,工作后因工作需要转入前端这一行,js的基础理论知识掌握情况基本为0,只知道jq一把梭,能写function能跑就行,也不愿意去看原型闭包这些比较绕的知识点
不过还是应了那句话,基础建设决定上层建筑,没有打好地基想牢固是很难的,只能缓慢爬坡,有了牢固的地基才能呈指数趋势起飞,还是需要掌握好这些基础知识,灵活运用
自问自答,全部简答,给个大致方向,具体每个问题的详细讲解还要去看对应的长篇大论的博客
原型和原型链
这个问题如果讲得多,可能15分钟讲不完,从几个关键点去回答,普通对象,函数对象,proto,prototype,以及最绕的Object和Function,就像鸡生蛋还是蛋生鸡,我当时学习的过程中不想去死记硬背,背了隔段时间就会忘,所以搞清楚顶层的Object和Function就好理解多了
- 普通对象只有__proto__,函数对象具有__proto__和prototype
- 顶层的Object和Function的关系(这一块是最绕的…)
- Object.proto、Array.__proto__等等都指向Function.prototype
- 特殊情况Function.__proto__指向自身Function.prototype,比较绕,不像一般的实例对象p.__proto__是指向父级Person.prototype
- Function.prototype.proto、Number.prototype.__proto__是对象,都指向Object.prototype
- 最终的Object.prototype指向null
- 面试可能会考量你对Object和Function互相关系的理解,出一个Object.prototype.toString和Object.toString比较
- 不扯了…太多了
js不像java是面向对象设计的语言,原型可以用来实现js中的继承,可以方法共享,避免重复占用浪费内存,具体看下面的文章吧
说说原型(prototype)、原型链和原型继承 - 知乎
JavaScript原型链的学习总结_IronKee的博客-优快云博客
闭包
简单讲,就是外部函数可以访问其他函数内部的变量,一种机制
- 全局作用域,函数作用域,闭包的本质是利用了作用域的机制,来达到外部作用域访问内部作用域的目的
- 另外就是让这些变量的值始终保持在内存中,不会在调用后被自动清除
- 面试官:说说作用域和闭包吧
- 如果你在F12中看过[[Scopes]]和其中的Closure,应该明白,具体参考这篇文章
js中,函数的闭包、作用域跟[[Scopes]]的关系 - 掘金 - JS学习理解之闭包和高阶函数
this指向,JS执行上下文,箭头函数this和普通函数区别
这个问题依然不好回答,有的时候明白但是讲不好,如果是结合题目去答可能会好一些
- 想搞清楚this,最好结合js执行上下文context去理解,简单讲就是谁调用,this就指向谁,具体看这篇讲的很清晰,this 的值到底是什么?一次说清楚 - 知乎
- 一篇文章看懂JS执行上下文
- 如果你对call很清楚,那this的理解应该就没问题,this就是call一个函数的时候,传入的第一个参数context,fn.call(obj),this就是obj,fn()等同于fn.call()等同于fn.call(undefined),非严格模式下,默认this是window
- 再看一下这一题,加深对this的理解吧,如何理解 (object.getName = object.getName)() 这段代码?
- 箭头函数的this是在定义函数时绑定的,不是在执行过程中绑定的。简单的说,函数在定义时,this就继承了定义函数的对象,解决了匿名函数this指向的问题,包括setTimeout和setInterval中使用this所造成的问题,ES6箭头函数里的this
- 箭头函数是匿名函数,不能作为构造函数,不能使用new,没有原型属性,this指向和箭头函数与普通函数的区别
call/apply/bind
call/apply/bind经常和this一起出现,也有些面试官会问你能不能手写call,如果理解call的过程,可以描述一下,我觉得没必要真的手写
- call、apply、bind 都是为了改变函数运行时上下文(this指向)而存在的,三者接收的第一个参数都是要绑定的this指向,apply的第二个参数是一个数组,call和bind的第二个及之后的参数作为函数实参按顺序传入,bind不会立即调用,执行的时候才调用,其他两个会立即调用。call,apply,bind
- 手写call的具体步骤
– 首先获取传入的上下文,没有的话指定为window
– context.func = this,拿到当前调用的this,将当前this赋值给上下文中的func,暂时保存
– 获取arguments
– 执行context.func
– 删除context.func - 手写call https://zhuanlan.zhihu.com/p/261332524
Function.prototype.call2 = function (context){ const ctx = context || window ctx.func = this //调用call2时,this是调用call2的方法(call2是在函数对象上) //测试用例中这个this打印出来是:[function a] const args = Array.from(arguments).slice(1)//保存参数 //通过在ctx中新建一个 函数对象等于调用时的对象 来调用执行来修改this指向 const res = arguments.length > 1 ? ctx.func(...args) : ctx.func() delete ctx.func//避免造成全局污染 return res } //测试用例: obj={c:2} function a(x,y){console.log(this,x,y)} a.call(obj,1,2)//{c:2} 1 2 a.call2(obj,1,2)//{c:2,func:[function a]} 1 2
- 手写apply和call差不多,参数处理成数组,手写bind需要return出去一个函数
new
同样有些面试官会问你能不能手写new,还是要理解new的过程,做了什么
- new一个构造函数,得到的实例继承了构造器的构造属性(this.name这些)以及原型上的属性
- 手写new https://www.cnblogs.com/echolun/p/10903290.html
function _new(fn,...rest){ //基于fn的prototype构建对象的原型 const thisObj = Object.create(fn.prototype); //将thisObj作为fn的this,继承其属性,并获取返回结果为result const result = fn.apply(thisObj,rest); //根据result对象的类型决定返回结果 return typeof result === "object" ? result : thisObj; } function Person(name,age){ this.name = name; this.age = age; } let person11 = new Person("Lee",21); let person22 = _new(Person,"Lee",21)
事件循环eventloop
这个题目比较高频,几个答题点,js单线程,宏任务,微任务,一次事件循环中哪个优先,浏览器中与nodejs中事件循环的区别
- 【JS】深入理解事件循环,这一篇就够了!(必看)
- 浏览器是多进程的,包含很多线程,其中就包含js引擎线程,也就是js主线程
- js是单线程的,任务分为同步任务和异步任务,同步任务即立即执行的任务,异步任务则会压入队列中,通过先进先出的机制进行协调
- 同步的进入主线程,即主执行栈,异步的进入任务队列。主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。
- 在事件循环中,每进行一次循环操作称为tick
- 规范中规定,task分为两大类, 分别是 Macro Task (宏任务)和 Micro Task(微任务), 并且每个宏任务结束后, 都要清空所有的微任务
- 宏任务主要包含:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
微任务主要包含:Promise、MutaionObserver、process.nextTick(Node.js 环境) - 一次事件循环中宏先于微,比如同时存在console.log setTimeout promise,则会先执行宏任务console.log或者执行其他同步任务,再读取任务队列清空微任务,执行微任务promise,再执行宏任务setTimeout
- nodejs中于浏览器中事件循环的区别,暂时了解不多,待补充…
防抖 节流
这个题目频率也不算很低,答题点有实现过程、思路和应用场景
- 防抖是触发高频事件后,n秒内函数只会执行一次,如果n秒内高频事件再次触发,则会重新计算时间,事件响应函数在一段事件后才执行,,如果这段时间内在次调用内再次调用,则重新计算执行时间,当预定时间内没有再次调用该函数,则执行事件处理逻辑函数
– 应用场景,比如页面滚动获取scrollTop值 - 节流指当高频事件触发时,稀释函数的执行频率,让其只会在n秒内执行一次
– 应用场景,比如点击登录按钮,1秒内只执行一次 - 具体实现参考手写防抖节流函数
深拷贝,如何避免循环引用
这个题目也算比较高频,考察的点也比较多,现实工作中如果用到深拷贝,建议使用成熟的lodash.js提供的深拷贝cloneDeep
- 简单的深拷贝,JSON.parse(JSON.stringify(对象 || 数组)),这种方法有局限性,它只适用于一般数据的拷贝(对象、数组)
- 时间对象会被转成字符串(Date对象的toJSON JSON.stringify转换JSON时日期时间不准确的解决方法)
- function、undefined会丢失
- RegExp、Error对象会被转成空{}
- NaN、Infinity和-Infinity会变成null
- 如果json里有对象是由构造函数生成的,则序列化的结果会丢弃对象的 constructor
- 如果对象中存在循环引用的情况也无法实现深拷贝
- 参考关于 JSON.parse(JSON.stringify(obj)) 实现深拷贝的一些坑
- 那如何实现深拷贝呢,那就比较难了,参考【每日一题】深拷贝时有循环引用怎么解决?-技术圈
变量提升
答题点,函数是一等公民,函数声明提升,函数表达式不提示,提升范围
- JavaScript变量提升
- JavaScript代码运行之前其实是有一个编译阶段的,变量提升就发生在编译阶段,它把变量和函数的声明提升至作用域的顶端
- 提升的部分只是变量声明,赋值语句和可执行的代码逻辑还保持在原地不动
- 提升只是将变量声明提升到变量所在的变量范围的顶端,并不是提升到全局范围
函数调用
4种方式
- 一般形式函数调用 fn()
- 方法调用
- call/apply调用
- 构造器模式调用 let p = new Person();p.fn()
javascript的执行函数的四种方式
JavaScript函数的4种调用方法详解
JS函数调用(4种方法)
一些常问到的不太常用的API
列举一些常问到的不太常用的api
- Object.create() ,创建一个新对象,使用现有的对象来提供新创建的对象的proto,Object.create()
- Object.assign(),合并对象
- hasOwnProperty(),判断对象上是否含有要查找的属性,不会去原型链上查找
- 在ES5的环境中,我们使用Object.getPrototype(obj)来获取原型对象,而在不支持ES5的环境中,我们可以考虑用__proto__这样的非标准方法来当做权宜之计
- JS数组reduce()方法详解及高级技巧
基础知识考察
let a={},a+1,a+‘1’,‘1’+1,‘1’-1,分别输出什么,这种问题靠猜没用,要掌握js设计规则去记忆
- 原始类型有Undefined Null Boolean String Number Symbol,引用类型有Object
- 类型转换只有三种转换为布尔值、数字、字符串
- 加法
– 运算中其中一方为字符串,那么就会把另一方也转换为字符串
– 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串(具体转换参照对象转原始类型) - 其他运算符
– 只要其中一方是数字,那么另一方就会被转为数字 - 所以a+1输出’[object object]1’,a+‘1’也是,‘1’+1会转换成字符串输出’11’,‘1’-1会转换成数字输出0
- 参考
– JS基础知识点(一)
– JS 基础知识点及常考面试题(一)
– JS-隐士类型转换‘1’+1、‘1’-1、++‘1’为什么不一样?
– 笔记:隐式转换规则 - 关于比较运算符规则更多,看这篇,前端知识点细节总结(一)
- 比较运算符规则 终极分析
未完待续,持续补充…