在去年末开始换工做,直到如今算是告了一个段落,断断续续的也面试了很多公司,如今回想起来,那段时间经历了被面试官手撕,被笔试题狂怼,悲伤的时候差点留下没技术的泪水。javascript
这篇文章我打算把我找工做遇到的各类面试题(每次面试完我都会总结)和我本身复习遇到比较有意思的题目,作一份汇总,年后是跳槽高峰期,也许能帮到一些小伙伴。css
先说下这些题目难度,大部分都是基础题,由于这段经历给个人感受就是,无论你面试的是高级仍是初级,基础的知识必定会问到,甚至会有必定的深度,因此基础仍是很是重要的。html
我将根据类型分为几篇文章来写:前端
面试总结:javascript 面试点汇总(万字长文)(已完成) 强烈你们看看这篇,面试中 js 是大头vue
面试总结:nodejs 面试点汇总(已完成)java
面试总结:浏览器相关 面试点汇总(已完成)node
面试总结:css 面试点汇总(已完成)webpack
面试总结:框架 vue 和工程相关的面试点汇总(已完成)git
面试总结:面试技巧篇(已完成)es6
六篇文章都已经更新完啦~
这篇文章是对 javascript
相关的题目作总结,内容有点长,大体算了下,有接近 2W 字,推荐用电脑阅读,欢迎朋友们先收藏在看。
先看看目录(这长图在手机上比较模糊,可点击图片看大图)
原型链这东西,基本上是面试必问,并且不是知识点还都是基于原型链扩展的,因此咱们先把原先链整明白。
咱们看一张网上很是流行的图
嗯,箭头有点多且有点绕,不要紧,咱们可逐步分析,咱们从结果倒推结论,这样更直观些,看代码
function person() { this.name = 10 } person.prototype.age = 10 const p = new person()
咱们经过断点看下 person 这个函数的内容
它是一个自定义的函数类型,看关键的两个属性 prototype
和 __proto__
,咱们一一分析
对 prototype
展开看,是个自定义的对象,这个对象有三个属性 age constructor __proto__
,age
的值是 10 ,那么能够得出经过person.prototype
赋值的参数都是在 prototype
这个对象中的。
点开 constructor
,发现这个属性的值就是指向构造器 preson
函数,其实就是循环引用,这时候就有点套娃的意思了
那么,根据字面意思, prototype
能够翻译成,原先对象,用于扩展属性和方法。
__proto__
分析对 __proto__
展开看看
person 中的 __proto__
是一个原始的 function 对象,在 function 对象中,又看到了 __proto__
这个属性,这时候它的值是原始的 Object 对象,在 Object 对象中又再次发现了 __proto__
属性,这时候 __proto__
等于 null
js 中数据类型分为两种,基本类型和对象类型,因此咱们能够这么猜想,person 是一个自定义的函数类型,它应该是属于函数这一家族下的,对于函数,咱们知道它是属于对象的,那么它们几个是怎么关联起来的呢?
没错,就是经过 __proto__
这个属性,而由这个属性组成的链,就叫作原型链。
根据上面的例子咱们,可得出,原型链的最顶端是 null
,往下是 Object
对象,并且只要是对象或函数类型都会有 __proto__
这个属性,毕竟你们都是 js-family 的一员嘛。
上面咱们已经知道了原型和原型链,那么对于 new 出来的对象,它们的关系又是怎么样的呢?继续断点分析
p
对象中有个 __proto__
属性,咱们已经知道这是个原型链,经过它能够找到咱们的祖先,展开 __proto__
,你们看到这里有没有发现很眼熟,在看一张图,
没错!p.__proto__
就是 person 函数的 prototype
,这一步也就是 new 的核心点(下个题目咱们会说到)。
那么 p 这实例的原型链是怎么样的? p.__proto__ => {constructor:func}.__proto__ => Object => null
对于实例对象来讲,原先链主要用来作什么呢?
__proto__
往上查找还有就是,光看文字的解释仍是有点费解的,要想深刻理解,仍是须要多动手断点调试,才能很快理顺。
若仍是不太理解实例对象的原型链关系,能够看下一题:解释构造函数
构造函数与普通函数在编码上没有区别,只要能够经过 new 来调用的就是构造函数。
那么什么函数不能够做为构造函数呢?
箭头函数不能够做为构造函数。
new
是一个语法糖,对执行的原理一步步拆分并本身写一个模拟 new 的函数:
obj
的 __proto__
属性指向构造函数的 prototype
apply
执行构造,并将当前 this
的指向改成 obj
obj
对象function objectFactory() { var obj = {}, Constructor = [].shift.call(arguments); obj.__proto__ = Constructor.prototype; var ret = Constructor.apply(obj, arguments); return typeof ret === 'object' ? ret : obj; }; function fnf() { this.x = 123 } let a2 = objectFactory(fnf) // 模拟 new fnf() console.log(a2.x) // 123
可看出并不复杂,关键点在第二步,设置对象的原型链,这也是建立实例对象的核心点。
js 中数据类型分为两类,一类是基本数据类型,一类是对象类型。
基本数据类型有:Number String Boolean Null Undefined BigInt Symbol
对象类型: Object
也叫引用类型
let a = 1 let a1 = '1' let a2 = true let a3 = null let a4 = undefined let a5 = Symbol let a6 = {} console.log(typeof(a),typeof(a1),typeof(a2),typeof(a3),typeof(a4),typeof(a5),typeof(a6)) // number string boolean object undefined function object
__proto__
逐层向上查找,经过 instanceof 也能够判断一个实例是不是其父类型或者祖先类型的实例。有这么个面试题
function person() { this.name = 10 } console.log(person instanceof person)
结果是 false
,看下 person 函数的原型链 person.
proto_ => Function.
proto => Object.
proto => null
,因此在原型链上是找不到 person
的
Null
Undefined
String
Number
Boolean
BigInt
Symbol
基本类型:存储在栈内存中,由于基本类型的大小是固定,在栈内能够快速查找。
引用类型:存储在堆内存中,由于引用类型的大小是不固定的,因此存储在堆内存中,而后栈内存中仅存储堆中的内存地址。
咱们在查找对象是从栈中查找,那么可得知,对于基本对象咱们是对它的值进行操做,而对于引用类型,咱们是对其引用地址操做。
var name = 'xiaoming' var name1 = name; // 值拷贝 var obj = {age:10} var obj1 = obj // 引用地址的拷贝,因此这两个对象指向同一个内存地址,那么他们实际上是同一个对象
关于函数的传参是传值仍是传引用呢?
不少人说基本类型传值,对象类型传引用,但严格来讲,函数参数传递的是值,上图能够看出,就算是引用类型,它在栈中存储的仍是一串内存地址,因此也是一个值。不过我以为不必过于纠结这句话,理解就行。
NaN 属性是表明非数字值的特殊值,该属性用于表示某个值不是数字。
NaN 是 Number 对象中的静态属性
typeof(NaN) // "number" NaN == NaN // false
那怎么判断一个值是不是 NAN 呢? 若支持 es6
,可直接使用 Number.isNaN()
若不支,可根据 NAN !== NAN
的特性
function isReallyNaN(val) { let x = Number(val); return x !== x; }
null 是基本类型之一,不是 Object 对象,至于为何?答曰:历史缘由,咱也不敢多问
typeof(null) // "object" null instanceof Object // false
那怎么判断一个值是 null 呢?可根据上面描述的特性,得
function isNull(a) { if (!a && typeof (a) === 'object') { return true } return false } console.log(isNull(0)) // false console.log(isNull(false))// false console.log(isNull('')) // false console.log(isNull(null)) // true
包装对象,只要是为了便于基本类型调用对象的方法。
包装对象有三种:String Number Boolean
这三种原始类型能够与实例对象进行自动转换,可把原始类型的值变成(包装成)对象,好比在字符串调用函数时,引擎会将原始类型的值转换成只读的包装对象,执行完函数后就销毁。
class 也是一个语法糖,本质仍是基于原型链,class 语义化和编码上更加符合面向对象的思惟。
对于 function
能够用 call apply bind
的方式来改变他的执行上下文,可是 class
却不能够,class 虽然本质上也是一个函数,但在转成 es5 (babel)作了一层代理,来禁止了这种行为。
Object.keys()
遍历由于涉及的代码较多,因此独立写一篇文章来总结,传送门: [js-实现继承的几种方式]()
先说下做用域的这个概念,做用域就是变量和函数的可访问范围,控制这个变量或者函数可访问行和生命周期(这个很重要)。
在 js 中是词法做用域,意思就是你的变量函数的做用域是由你的编码中的位置决定的,固然能够经过 apply bind
等函数进行修改。
在 ES6 以前,js 中的做用域分为两种:函数做用域和全局做用域。
全局做用域顾名思义,浏览器下就是 window
,做用域链的顶级就是它,那么只要不是被函数包裹的变量或者函数,它的做用域就是全局。
而函数做用域,就是在函数的体内声明的变量、函数及函数的参数,它们的做用域都是在这个函数内部。
那么函数中的未在该函数内定义的变量呢?这个变量怎么获取呢?这就是做用域链的概念了。
咱们知道函数在执行时是有个执行栈,在函数执行的时候会建立执行环境,也就是执行上下文,在上下文中有个大对象,保存执行环境定义的变量和函数,在使用变量的时候,就会访问这个大对象,这个对象会随着函数的调用而建立,函数执行结束出栈而销毁,那么这些大对象组成一个链,就是做用域链。
那么函数内部未定义的变量,就会顺着做用域链向上查找,一直找到同名的属性。
看下面这个栗子
var a = 10; function fn() { var b = 20; function bar() { console.log(a + b) // a 一直往上找,直到最高层级找到了, b 往上找,在函数 fn 这一层级的上下文中找到了 b=20 ,就没有继续往上找 } return bar } b = 200; var x = fn(); x()
在看看闭包的做用域,只要存在函数内部调用,执行栈中就会保留父级函数和函数对于的做用域,因此父函数的做用域在子函数的做用域链中,直到子函数被销毁,父级做用域才会释放,来个很常见的面试题
function test() { for (var index = 0; index < 3; index++) { setTimeout(() => { console.log('index:' + index) }) } } test() // index:3 // index:3 // index:3
执行结果是 3个3,由于js的事件循环机制,就不细说,那么咱们想让它按顺序输出,咋办呢?
思路就是,由于定时器的回调确定是在循环结束后才执行,那时候 index 已是3了,那么能够利用上面说的闭包中的做用域链,在子函数中去引用父级的变量,这样子函数没有被销毁前,这个变量是会一直存在的,因此咱们能够这么改。
function test() { for (var index = 0; index < 3; index++) { ((index) => { setTimeout(() => { console.log('index:' + index) }) })(index) } }
咱们在看一道面试题
function f(fn, x) { console.log('into') if (x < 1) { f(g, 1); } else { fn(); } function g() { console.log('x' + x); } } function h() { } f(h, 0) // x 0
逻辑很简单,但面试题就是这么鬼精,越是简单越有坑。
g 函数中的 x 变量是引用父级的,而 f 函数执行了两次,x 变量依次为 0 1,在 f(h,0) 这个函数执行的时候,这个函数的做用域中的 x=0,这个时候 g 函数中引用的 x 就是当前执行上下文中的 x=0 这个变量,但这个函数还没被执行,接着到了 f(g, 1) 执行,这一层执行上下文中的 x=1 ,但注意两次f执行的做用域不是同一个对象,是做用域链上两个独立的对象,最后到了 fn() ,这个fn是一个参数,也就是在 f(h,0) 执行的时候 g 函数,那么 g 函数在这里被执行,g 打印出来的 x 就是 0 。
块级做用域: let const
的出现就是为了解决 js 中没有块级做用域的弊端。
其余小点:
做用域和执行上下文的区别,看下引擎执行脚本的两个阶段
解释阶段: 词法分析 -> 语法分析 -> 做用域规则肯定
执行阶段: 建立执行上下文 -> 执行函数代码 -> 垃圾回收
参考链接:
https://segmentfault.com/a/11...
https://www.cnblogs.com/dolph...
var: 解析器在对js解析时,会将脚本扫描一遍,将变量的声明提早到代码块的顶部,赋值仍是在原先的位置,若在赋值前调用,就会出现暂时性死区,值为 undefined
let const:不存在在变量提高,且做用域是存在于块级做用域下,因此这两个的出现解决了变量提高的问题,同时引用块级做用域。
注:变量提高的缘由是为了解决函数互相调用的问题。
其实就是问对 Object.defineProperty
的掌握程度。
相关的属性以下:
[[Configurable]]:表示可否经过 delete 删除属性从而从新定义属性,可否修改属性的特性,或者可否把属性修改成访问器属性。
[[Enumerable]]:表示可否经过 for-in 循环返回属性。
[[Writable]]:表示可否修改属性的值。
[[Value]]:包含这个属性的值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为 undefined。
数据属性能够直接定义,如 var p = {name:'xxx'}
这个 name
就是数据属性,直接定义下,相关属性值都是 true ,若是要修改默认的定义值,那么使用 Object.defineProperty()
方法,以下面这个栗子
var p = { name:'dage' } Object.defineProperty(p,'name',{ value:'xxx' }) p.name = '4rrr' console.log(p.name) // 4rrr Object.defineProperty(p,'name',{ writable:false, value:'again' }) p.name = '4rrr' console.log(p.name) // again
访问器属性不包含数据值,没有 value 属性,有 get set
属性,经过这两个属性来对值进行自定义的读和写,能够理解为取值和赋值前的拦截器,相关属性以下:
[[Configurable]]:表示可否经过 delete 删除属性从而从新定义属性,可否修改属性的特性,或者可否把属性修改成数据属性,默认 false
[[Enumerable]]:表示可否经过 for-in 循环返回属性,默认 false
[[Get]]:在读取属性时调用的函数。默认值为 undefined
[[Set]]:在写入属性时调用的函数。默认值为 undefined
get set
的特性,能够实现对象的代理, vue
就是经过这个实现数据的劫持。二者的相同点:都有 Configurable 和 Enumerable 属性。
一个简单的小demo
var p = { name:'' } Object.defineProperty(p,'name',{ get:function(){ return 'right yeah !' }, set:function(val){ return 'handsome '+val } }) p.name = `xiaoli` console.log(p.name) // right yeah !
参考链接:
https://cloud.tencent.com/dev...
https://developer.mozilla.org...
在 Object 中存在这个两个方法,继承Object的对象能够重写方法。这两个方法主要用于隐式转换,好比
js 不一样于其余语言,两个不一样的数据类型能够进行四则运算和判断,这就归功于隐式转换了,隐式转换我就不详细介绍了,由于我没有被问到~
1 + '1' // 11 :整型 1 被转换成字符串 '1',变成了 '1' + '1' = '11' 2 * '3' // 6 :字符串 '3' 被转换成整型 3 ,变成了 2 * 3 = 6
那么咱们也能够对自定义的对象重写这两个函数,以便进行隐式转换
let o = function () { this.toString = () => { return 'my is o,' } this.valueOf = () => { return 99 } } let n = new o() console.log(n + 'abc') // 99abc console.log(n * 10) // 990 // 有没有很酷炫
当这两个函数同时存在时候,会先调用 valueOf
,若返回的不是原始类型,那么会调用 toString
方法,若是这时候 toString
方法返回的也不是原始数据类型,那么就会报错 TypeError: Cannot convert object to primitive value
以下
let o = function () { this.toString = () => { console.log('into toString') return { 'string': 'ssss' } } this.valueOf = () => { console.log('into valueOf') return { 'val': 99 } } } let n = new o() console.log(n + 'xx') //into valueOf //into toString // VM1904:12 Uncaught TypeError: Cannot convert object to primitive value
(很是感谢评论区伙伴的提醒)
arguments
是一个类数组对象,能够获取到参数个数和参数列表数组,对于不定参数的函数,能够用 arguments 获取参数。
那么对于箭头函数有没有 arguments 呢? 须要看具体执行的场景了
// 箭头函数 let aa1 = (...args) => { let bb = [].slice.call(arguments, 0) let a = arguments[0] let b = arguments[1] let c = arguments[2] console.log(a + b + c) } // 正常的函数 let aa = function (...args) { let bb = [].slice.call(arguments, 0) let a = arguments[0] let b = arguments[1] let c = arguments[2] console.log(a + b + c) } aa(1, 2, 3) aa1(1, 2, 3)
分别观察如下两个场景的执行结果
直接看结果
很明显,在浏览器中 arguments
是不存在的
结果(为了辨认,输出前加了段字符串)
执行过程没有报错,说明 arguments
是存在的,那为啥结果不是预期的 6 呢?
咱们对箭头函数打断点看看
arguments 对象看着没啥问题,传入的参数也看到了
咱们看看经过数组方式获取到的值
居然是这些东西,这些是当前脚本执行的模块信息,并非咱们预期的参数列表
arguments
arguments
,可经过其获取参数长度,但不能经过改对象获取参数列表(我也不太懂这个对象的原理,还请知道的伙伴在评论区告知,谢谢)
浮点数的精度丢失不只仅是js的问题, java 也会出现精度丢失的问题(没有黑java),主要是由于数值在内存是由二进制存储的,而某些值在转换成二进制的时候会出现无限循环,因为位数限制,无限循环的值就会采用“四舍五入法”截取,成为一个计算机内部很接近数字,即便很接近,可是偏差已经出现了。
举个栗子
0.1 + 0.2 = 0.30000000000000004 // 0.1 转成二进制会无限循环 // "0.000110011001100110011001100110011001100110011001100..."
那么如何避免这问题呢?解决办法:可在操做前,放大必定的倍数,而后再除以相同的倍数
(0.1 *100 + 0.2*100) / 100 = 0.3
js 的 number 采用 64位双精度存储
JS 中能精准表示的最大整数是 Math.pow(2, 53)
推荐一个开源工具 (number-precision)[https://github.com/nefe/numbe...]
toFixed
对于四舍六入没问题,但对于尾数是 5
的处理就很是诡异
(1.235).toFixed(2) // "1.24" 正确 (1.355).toFixed(2) // "1.35" 错误
我也没明白为啥这么设计,严格的四舍五入能够采用如下函数
// 使用 Math.round 能够四舍五入的特性,把数组放大必定的倍数处理 function round(number, precision) { return Math.round(+number + 'e' + precision) / Math.pow(10, precision); }
原理是,Math.round
是能够作到四舍五入的,可是仅限于正整数,那么咱们能够放大至保留一位小数,计算完成后再缩小倍数。
10 进制转其余进制:Number(val).toString([2,8,10,16])
其余进制转成10进制:Number.parseInt("1101110",[2,8,10,16])
其余进制互转:先将其余进制转成 10 进制,在把 10 进制转成其余进制
ArrayBuffer: 用来表示通用的、固定长度的原始二进制数据缓冲区,做为内存区域,能够存放多种类型的数据,它不能直接读写,只能经过视图来读写。
同一段内存,不一样数据有不一样的解读方式,这就叫作“视图”(view),视图的做用是以指定格式解读二进制数据。目前有两种视图,一种是 TypedArray
视图,另外一种是 DataView
视图,二者的区别主要是字节序,前者的数组成员都是同一个数据类型,后者的数组成员能够是不一样的数据类型。
Blob: 也是存放二进制的容器,经过 FileReader
进行转换。
以前有作过简单的总结,你们能够看看:nodejs 二进制与Buffer
毕竟对这块应用的比较少,推荐一篇文章给你们 二进制数组
这个问题出场率很高呀!常见的有以下几个:
回调函数
:经过嵌套调用实现Generator
: 异步任务的容器,生成器本质上是一种特殊的迭代器, Generator 执行后返回的是个指针对象,调用对象里的 next 函数,会移动内部指针,分阶段执行 Generator
函数 ,指向 yield
语句,返回一个对象 {value:当前的执行结果,done:是否结束}promise
: 而是一种新的语法糖, Promise 的最大问题是代码冗余,经过 then 传递执行权,由于需求手动调用 then 方法,当异步函数多的时候,原来的语义变得很不清楚co
: 把 Generator 和 Promise 封装,达到自动执行async\await
: 目前是es7草案,可经过 bable webpack
等工具提早使用,目前原生浏览器支持还不太好。其本质上是语法糖,跟 co 库同样,都是对 generator+promise
的封装,不过相比 co ,语义化更好,能够像普通函数同样调用,且大几率是将来的趋势。Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。
Generator 的核心是能够暂停函数执行,而后在从上一次暂停的位置继续执行,关键字 yield 标识暂停的位置。
Generator 函数返回一个迭代器对象,并不会当即执行函数里面的方法,对象中有 next() 函数,函数返回 value 和 done 属性,value 属性表示当前的内部状态的值,done 属性标识是否结束的标志位。
Generator 的每一步执行是经过调用 next() 函数,next 方法能够带一个参数,该参数就会被看成上一个yield表达式的返回值。
执行的步骤以下:
(1)遇到 yield
表达式,就暂停执行后面的操做,并将紧跟在 yield
后面的那个表达式的值,做为返回的对象的 value
属性的值。
(2)下一次调用 next 方法时,再继续往下执行,直到遇到下一个 yield
表达式。
(3)若是没有再遇到新的 yield
表达式,就一直运行到函数结束,直到 return 语句为止,并将 return 语句后面的表达式的值,做为返回的对象的value属性值。
(4)若是该函数没有 return 语句,则返回的对象的 value 属性值为 undefined。
注意: yield
表达式,自己是没有值的,须要经过 next() 函数的参数将值传进去。
let go = function* (x) { console.log('one', x) let a = yield x * 2 console.log('two', a) let b = yield x + 1 sum = a + b return sum } let g = go(10) let val = g.next() while (!val.done) { val = g.next(val.value) } console.log(val)
可见 Generator 的弊端很明显,执行流程管理不方便,异步返回的值须要手动传递,编码上较容易出错。
Promise 已是 ES6 的规范了,相比 Generator ,设计的更加合理和便捷。
看看Promise的规范:
promise 的每一个操做返回的都是 promise 对象,可支持链式调用。经过 then 方法执行回调函数,Promise 的回调函数是放在事件循环中的微队列。
co 用 promise 的特性,将 Generator 包裹在 Promise 中,而后循环执行 next 函数,把 next 函数返回的的 value 用 promise 包装,经过 then.resolve 调用下一个 next 函数,并将值传递给 next 函数,直到 done 为 true,最后执行包裹 Generator 函数的 resolve。
咱们看下源码,源码作了截取
function co(gen) { return new Promise(function(resolve, reject) { // 最外层是一个 Promise 对象 if (typeof gen === 'function') gen = gen.apply(ctx, args); if (!gen || typeof gen.next !== 'function') return resolve(gen); onFulfilled(); function onFulfilled(res) { var ret; try { ret = gen.next(res); // 将上一步的返回值传递给 next } catch (e) { return reject(e); } next(ret); // 将上一步执行结果转换成 promise return null; } /** * Get the next value in the generator, * return a promise. * * @param {Object} ret * @return {Promise} * @api private */ function next(ret) { if (ret.done) return resolve(ret.value); // done为true,就表示执行结束,resolve结果出去 var value = toPromise.call(ctx, ret.value); // toPromise 是个工具函数,将对象转换成 promise,能够理解返回的 value 就是 promise if (value && isPromise(value)) return value.then(onFulfilled, onRejected); // then 函数执行回调 onFulfilled return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, ' // 异常处理 + 'but the following object was passed: "' + String(ret.value) + '"')); } }); }
这是必考题呀,盆友们,这个可阅读我之前写的一篇文章,传送门: js 事件循环
这个东西有点多,能够看我以前的一篇总结,传送门:面试官让我解释前端模块化
为何须要垃圾回收:由于对象须要占用内存,而内存资源是有限的。
js 会周期性的对不在使用的对象销毁,释放内存,关键点就在于怎么识别哪些对象是垃圾。
垃圾对象:对象没有被引用,或者几个对象造成循环引用,可是根访问不到他们,这些都是可回收的垃圾。
垃圾回收的两种机制:标记清除和引用计数
垃圾收集器在运行的时候会给存储在内存中的全部变量都加上标记,而后,它会去掉环境中的变量以及被环境中的变量引用的标记,而在此以后再被加上标记的变量将被视为准备删除的变量,缘由是环境中的变量已经没法访问到这些变量了。
最后。垃圾收集器完成内存清除工做,销毁那些带标记的值,并回收他们所占用的内存空间。
好比说函数中声明了一个变量,就作一个标记,当函数执行完成,退出执行栈,这个变量的标记就变成已使用完。
目前主流浏览器采用的是这个策略
跟踪每一个值被引用的次数,声明一个变量后,这个变量每被其余变量引用一次,就加 1 ,若是变量引用释放了,就减 1,当引用次数为 0 的时候,对象就被清理。但这个有个循环引用的弊端,因此应用的比较少。
经过在脚本的最顶端放上一个特定语句 "use strict";
整个脚本就可开启严格模式语法。
严格模式下有如下好处:
如如下具体的场景:
map
的 key
能够是任意类型,在 map
内部有两个数组,分别存放 key
和 value
,用下标保证二者的一一对应,在对 map
操做时,内部会遍历数组,时间复杂度O(n),其次,由于数组会一直引用每一个键和值,回收算法无法回收处理,可能会致使内存泄露。
相比之下, WeakMap
的键值必须是对象,持有的是每一个键对象的 弱引用
,这意味着在没有其余引用存在时垃圾回收能正确进行。
const wm1 = new WeakMap(); const o1 = {}; wm1.set(o1, 37); // 当 o1 对象被回收,那么 WeakMap 中的值也被释放
我也不知道为何会有这种笔试题...
split(): 方法使用指定的分隔符字符串将一个String对象分割成子字符串数组
slice(): 方法提取某个字符串的一部分,并返回一个新的字符串,且不会改动原字符串
substring(): 方法返回一个字符串在开始索引到结束索引之间的一个子集, 或从开始索引直到字符串的末尾的一个子集
slice(): 方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。
splice(): 方法经过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。
push(): 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
pop(): 方法从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。
shift():方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。
unshift(): 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度(该方法修改原有数组)。
这题主要仍是考察对原型链的理解
Array.isArray()
ES6 apiobj instanceof Array
原型链查找obj.constructor === Array
构造函数类型判断Object.prototype.toString.call(obj) === '[object Array]'
toString返回表示该对象的字符串,若这个方法没有被覆盖,那么默认返回 "[object type]"
,其中 type
是对象的类型。须要准确判断类型的话,建议使用这种方法
map 的比较简单就不写了,我写个 reduce
处理 async/await
的 demo
const sleep = time => new Promise(res => setTimeout(res, time)) async function ff(){ let aa = [1,2,3] let pp = await aa.reduce(async (re,val)=>{ let r = await re; await sleep(3000) r += val; return Promise.resolve(r) },Promise.resolve(0)) console.log(pp) // 6 } ff()
闭包:定义在一个函数内部的函数,内部函数持有外部函数内变量的引用,这个内部的函数有本身的执行做用域,能够避免外部污染。
关于闭包的理解,能够说是一千个读者就有一千个哈姆雷特,找到适合本身理解和讲述的就行。
场景有:
这题面试官估计是想知道你是否是真的用过 es6 吧
扩展运算符(…)也会调用默认的 Iterator 接口。
扩展运算符主要用在不定参数上,能够将参数转成数组形式
function fn(...arg){ console.log(arg) // [ 1, 2, 3 ] } fn(1,2,3)
首先来一句话归纳:进程和线程都是一个时间段的描述,都是对CPU工做时间段的描述。
当一个任务获得 CPU 资源后,须要加载执行这个任务所须要的执行环境,也叫上下文,进程就是包含上下文切换的程序执行时间总和 = CPU加载上下文 + CPU执行 + CPU保存上下文。可见进程的颗粒度太大,每次都须要上下文的调入,保存,调出。
若是咱们把进程比喻为一个运行在电脑上的软件,那么一个软件的执行不多是一条逻辑执行的,一定有多个分支和多个程序段,就比如要实现程序A,实际分红 a,b,c等多个块组合而成。
那么这里具体的执行就是:程序A获得CPU => CPU加载上下文 => 开始执行程序A的a小段 => 而后执行A的b小段 => 而后再执行A的c小段 => 最后CPU保存A的上下文。这里a,b,c 的执行共享了A的上下文,CPU在执行的时候没有进行上下文切换的。
a,b,c 咱们就是称为线程,就是说线程是共享了进程的上下文环境,是更为细小的 CPU 执行时间段。
函数式编程的两个核心:合成和柯里化,以前对函数式编程作过总结,传送门:【面试官问】你懂函数式编程吗?
先给面试官简单说下什么是递归函数:函数内部循环调用自身的就是递归函数,若函数没有执行完毕,执行栈中会一直保持函数相关的变量,一直占用内存,当递归次数过大的时候,就可能会出现内存溢出,也叫爆栈,页面可能会卡死。
因此为了不出现这种状况,能够采用尾递归。
尾递归:在函数的最后一步是调用函数,进入下一个函数不在须要上一个函数的环境了,内存空间 O(n) 到 O(1) 的优化 ,这就是尾递归。
尾递归的好处:能够释放外层函数的调用栈,较少栈层级,节省内存开销,避免内存溢出。
网上不少用斐波那契数列做为栗子,但我偏不,我用个数组累加的栗子
function add1(arr) { if (arr.length === 0) { return 0 } return add1(arr.slice(1)) + arr[0] // 还有父级函数中 arr[0] 的引用 } function add(arr, re) { if (arr.length === 0) { return re + 0 } else { return add(arr.slice(1), arr[0] + re) // 仅仅是函数调用 } } console.log(add([1, 2, 3, 4], 0)) // 10 console.log(add1([1, 2, 3, 4])) // 10
二者都是订阅-通知的模式,区别在于:
观察者模式:观察者和订阅者是互相知道彼此的,是一个紧耦合的设计
发布-订阅:观察者和订阅者是不知道彼此的,由于他们中间是经过一个订阅中心来交互的,订阅中心存储了多个订阅者,当有新的发布的时候,就会告知订阅者
设计模式的名词实在有点多且绕,我画个简单的图:
这个就问到了一次,因此简单进行了了解。
简单来讲,WebSocket 是应用层协议,基于 tcp,与HTTP协议同样位于应用层,都是TCP/IP协议的子集。
HTTP 协议是单向通讯协议,只有客户端发起HTTP请求,服务端才会返回数据。而 WebSocket 协议是双向通讯协议,在创建链接以后,客户端和服务器均可以主动向对方发送或接受数据。
参考资料:
http://www.ruanyifeng.com/blo...
以上就是 javascript
相关的题目汇总,后续遇到有表明性的题目还会继续补充。
文章中若有不对的地方,欢迎小伙伴们多多指正。
若是你喜欢探讨技术,欢迎添加我微信一块儿学习探讨,你们都是同行,很是期待能与大伙聊技术、聊爱好。
下面是个人微信二维码,可扫码添加