好买网 GoodMai IT交易平台
注意:每道题前面出现的 (xx) 数字代表这道题出现的频次,此 JS 基础是基于 30+ 篇前端面经整理出的问题和对应的回答、参考链接等。文章内容为拿到 Offer 的本人整理。
JavaScript 使用 Number 类型来表示数字(整数或浮点数),遵循 IEEE 754 标准,通过 64 位来表示一个数字(1 + 11 + 52)
最大安全数字:Number.MAX_SAFE_INTEGER = Math.pow(2, 53) - 1,转换成整数就是 16 位,所以 0.1 === 0.1,是因为通过 toPrecision(16) 去有效位之后,两者是相等的。
在两数相加时,会先转换成二进制,0.1 和 0.2 转换成二进制的时候尾数会发生无限循环,然后进行对阶运算,JS 引擎对二进制进行截断,所以造成精度丢失。
所以总结:精度丢失可能出现在进制转换和对阶运算中
基本类型:Number、Boolean、String、null、undefined、symbol(ES6 新增的),BigInt(ES2020) 引用类型:Object,对象子类型(Array,Function)
Math.pow(2, 53) ,53 为有效数字,会发生截断,等于 JS 能支持的最大数字。
浅克隆:
function shallowClone(obj) { let cloneObj = {}; for (let i in obj) { cloneObj[i] = obj[i]; } return cloneObj; }
深克隆:
且当事件处于目标阶段时,事件调用顺序决定于绑定事件的书写顺序,按上面的例子为,先调用冒泡阶段的事件处理程序,再调用捕获阶段的事件处理程序。依次alert出“子集冒泡”,“子集捕获”。
function deepCopy(obj) { if (typeof obj === 'object') { var result = obj.constructor === Array ? [] : {}; for (var i in obj) { result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i]; } } else { var result = obj; } return result; }
事件流是网页元素接收事件的顺序,"DOM2级事件"规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。 首先发生的事件捕获,为截获事件提供机会。然后是实际的目标接受事件。最后一个阶段是时间冒泡阶段,可以在这个阶段对事件做出响应。 虽然捕获阶段在规范中规定不允许响应事件,但是实际上还是会执行,所以有两次机会获取到目标对象。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>事件冒泡</title> </head> <body> <div> <p id="parEle">我是父元素 <span id="sonEle">我是子元素</span></p> </div> </body> </html> <script type="text/javascript"> var sonEle = document.getElementById('sonEle'); var parEle = document.getElementById('parEle'); parEle.addEventListener('click', function () { alert('父级 冒泡'); }, false); parEle.addEventListener('click', function () { alert('父级 捕获'); }, true); sonEle.addEventListener('click', function () { alert('子级冒泡'); }, false); sonEle.addEventListener('click', function () { alert('子级捕获'); }, true); </script>
当容器元素及嵌套元素,即在捕获阶段
又在冒泡阶段
调用事件处理程序时:事件按DOM事件流的顺序执行事件处理程序:
基于发布订阅模式,就是在浏览器加载的时候会读取事件相关的代码,但是只有实际等到具体的事件触发的时候才会执行。
比如点击按钮,这是个事件(Event),而负责处理事件的代码段通常被称为事件处理程序(Event Handler),也就是「启动对话框的显示」这个动作。
在 Web 端,我们常见的就是 DOM 事件:
DOM0 级事件,直接在 html 元素上绑定 on-event,比如 onclick,取消的话,dom.onclick = null,同一个事件只能有一个处理程序,后面的会覆盖前面的。
DOM2 级事件,通过 addEventListener 注册事件,通过 removeEventListener 来删除事件,一个事件可以有多个事件处理程序,按顺序执行,捕获事件和冒泡事件
return {}
、 return null
, return 1
, return true
会发生什么情况?如果函数返回一个对象,那么new 这个函数调用返回这个函数的返回对象,否则返回 new 创建的新对象
symbol
有什么用处可以用来表示一个独一无二的变量防止命名冲突。但是面试官问还有吗?我没想出其他的用处就直接答我不知道了,还可以利用 symbol
不会被常规的方法(除了 Object.getOwnPropertySymbols
外)遍历到,所以可以用来模拟私有变量。
主要用来提供遍历接口,布置了 symbol.iterator
的对象才可以使用 for···of
循环,可以统一处理数据结构。调用之后回返回一个遍历器对象,包含有一个 next 方法,使用 next 方法后有两个返回值 value 和 done 分别表示函数当前执行位置的值和是否遍历完毕。
Symbol.for() 可以在全局访问 symbol
闭包是指有权访问另外一个函数作用域中的变量的函数
JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。
Not a Number,表示非数字,typeof NaN === 'number'
一般非基础类型进行转换时会先调用 valueOf,如果 valueOf 无法返回基本类型值,就会调用 toString
[] + {} 和 {} + [] 复制代码
宽松相等允许进行强制类型转换,而严格相等不允许
字符串与数字
转换为数字然后比较
其他类型与布尔类型
对象与非对象
假值列表
它们都是函数的方法
call: Array.prototype.call(this, args1, args2])
apply: Array.prototype.apply(this, [args1, args2])
:ES6 之前用来展开数组调用, foo.appy(null, [])
,ES6 之后使用 ... 操作符
四条规则:
function foo() { console.log(this.a); } var a = 2; foo();
隐式绑定:调用位置是否有上下文对象,或者是否被某个对象拥有或者包含,那么隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。而且,对象属性链只有上一层或者说最后一层在调用位置中起作用
function foo() { console.log(this.a); } var obj = { a: 2, foo: foo, } obj.foo(); // 2
function foo() { console.log(this.a); } var obj = { a: 2 }; foo.call(obj);
显示绑定之硬绑定
function foo(something) { console.log(this.a, something); return this.a + something; } function bind(fn, obj) { return function() { return fn.apply(obj, arguments); }; } var obj = { a: 2 } var bar = bind(foo, obj);
New 绑定,new 调用函数会创建一个全新的对象,并将这个对象绑定到函数调用的 this。
function foo(a) { this.a = a; } var bar = new foo(2); console.log(bar.a)
// call Function.prototype.call = function (context, ...args) { context = context || window; const fnSymbol = Symbol("fn"); context[fnSymbol] = this; context[fnSymbol](...args); delete context[fnSymbol]; }
// apply Function.prototype.apply = function (context, argsArr) { context = context || window; const fnSymbol = Symbol("fn"); context[fnSymbol] = this; context[fnSymbol](...argsArr); delete context[fnSymbol]; }
// bind Function.prototype.bind = function (context, ...args) { context = context || window; const fnSymbol = Symbol("fn"); context[fnSymbol] = this; return function (..._args) { args = args.concat(_args); context[fnSymbol](...args); delete context[fnSymbol]; } }
setTimeout(fn, 0)
多久才执行,Event LoopsetTimeout 按照顺序放到队列里面,然后等待函数调用栈清空之后才开始执行,而这些操作进入队列的顺序,则由设定的延迟时间来决定
class MyPromise { constructor(fn) { this.callbacks = []; this.state = "PENDING"; this.value = null; fn(this._resolve.bind(this), this._reject.bind(this)); } then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => this._handle({ onFulfilled: onFulfilled || null, onRejected: onRejected || null, resolve, reject, }) ); } catch(onRejected) { return this.then(null, onRejected); } _handle(callback) { if (this.state === "PENDING") { this.callbacks.push(callback); return; } let cb = this.state === "FULFILLED" ? callback.onFulfilled : callback.onRejected; if (!cb) { cb = this.state === "FULFILLED" ? callback.resolve : callback.reject; cb(this.value); return; } let ret; try { ret = cb(this.value); cb = this.state === "FULFILLED" ? callback.resolve : callback.reject; } catch (error) { ret = error; cb = callback.reject; } finally { cb(ret); } } _resolve(value) { if (value && (typeof value === "object" || typeof value === "function")) { let then = value.then; if (typeof then === "function") { then.call(value, this._resolve.bind(this), this._reject.bind(this)); return; } } this.state === "FULFILLED"; this.value = value; this.callbacks.forEach((fn) => this._handle(fn)); } _reject(error) { this.state === "REJECTED"; this.value = error; this.callbacks.forEach((fn) => this._handle(fn)); } } const p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error("fail")), 3000); }); const p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000); }); p2.then((result) => console.log(result)).catch((error) => console.log(error));
构造调用:
return {}
、 return null
, return 1
, return true
会发生什么情况?如果函数返回一个对象,那么new 这个函数调用返回这个函数的返回对象,否则返回 new 创建的新对象
symbol
有什么用处可以用来表示一个独一无二的变量防止命名冲突。但是面试官问还有吗?我没想出其他的用处就直接答我不知道了,还可以利用 symbol
不会被常规的方法(除了 Object.getOwnPropertySymbols
外)遍历到,所以可以用来模拟私有变量。
主要用来提供遍历接口,布置了 symbol.iterator
的对象才可以使用 for···of
循环,可以统一处理数据结构。调用之后回返回一个遍历器对象,包含有一个 next 方法,使用 next 方法后有两个返回值 value 和 done 分别表示函数当前执行位置的值和是否遍历完毕。
Symbol.for() 可以在全局访问 symbol
闭包是指有权访问另外一个函数作用域中的变量的函数
JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。
ES5 中只存在两种作用域:全局作用域和函数作用域。在 JavaScript 中,我们将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套子作用域中根据标识符名称进行变量(变量名或者函数名)查找
首先要了解作用域链,当访问一个变量时,编译器在执行这段代码时,会首先从当前的作用域中查找是否有这个标识符,如果没有找到,就会去父作用域查找,如果父作用域还没找到继续向上查找,直到全局作用域为止,,而作用域链,就是有当前作用域与上层作用域的一系列变量对象组成,它保证了当前执行的作用域对符合访问权限的变量和函数的有序访问。
当前环境中存在指向父级作用域的引用
闭包是一种特殊的对象,它由两部分组成:执行上下文(代号 A),以及在该执行上下文中创建的函数 (代号 B),当 B 执行时,如果访问了 A 中变量对象的值,那么闭包就会产生,且在 Chrome 中使用这个执行上下文 A 的函数名代指闭包。
Object.keys(obj).length === 0
手写题:在线编程,getUrlParams(url,key); 就是很简单的获取url的某个参数的问题,但要考虑边界情况,多个返回值等等
onload 是所以加载完成之后执行的
onclick 和 addEventListener
冒泡和捕获
function Person(name) { this.name = name; } Person.prototype.constructor = Person
在发生 new 构造函数调用时,会将创建的新对象的 [[Prototype]] 链接到 Person.prototype 指向的对象,这个机制就被称为原型链继承
方法定义在原型上,属性定义在构造函数上
首先要说一下 JS 原型和实例的关系:每个构造函数 (constructor)都有一个原型对象(prototype),这个原型对象包含一个指向此构造函数的指针属性,通过 new 进行构造函数调用生成的实例,此实例包含一个指向原型对象的指针,也就是通过 [[Prototype]] 链接到了这个原型对象
然后说一下 JS 中属性的查找:当我们试图引用实例对象的某个属性时,是按照这样的方式去查找的,首先查找实例对象上是否有这个属性,如果没有找到,就去构造这个实例对象的构造函数的 prototype 所指向的对象上去查找,如果还找不到,就从这个 prototype 对象所指向的构造函数的 prototype 原型对象上去查找
什么是原型链:这样逐级查找形似一个链条,且通过 [[Prototype]] 属性链接,所以被称为原型链
什么是原型链继承,类比类的继承:当有两个构造函数 A 和 B,将一个构造函数 A 的原型对象的,通过其 [[Prototype]] 属性链接到另外一个 B 构造函数的原型对象时,这个过程被称之为原型继承。
标准答案更正确的解释
什么是原型链?
当对象查找一个属性的时候,如果没有在自身找到,那么就会查找自身的原型,如果原型还没有找到,那么会继续查找原型的原型,直到找到 Object.prototype 的原型时,此时原型为 null,查找停止。 这种通过 通过原型链接的逐级向上的查找链被称为原型链
什么是原型继承?
一个对象可以使用另外一个对象的属性或者方法,就称之为继承。具体是通过将这个对象的原型设置为另外一个对象,这样根据原型链的规则,如果查找一个对象属性且在自身不存在时,就会查找另外一个对象,相当于一个对象可以使用另外一个对象的属性和方法了。
是基于原型的动态语言,主要独特特性有 this、原型和原型链。
JS 严格意义上来说分为:语言标准部分(ECMAScript)+ 宿主环境部分
2015 年发布 ES6,引入诸多新特性使得能够编写大型项目变成可能,标准自 2015 之后以年号代号,每年一更
Array.isArray
是类数组,是属于鸭子类型的范畴,长得像数组,
为 JS 添加类型支持,以及提供最新版的 ES 语法的支持,是的利于团队协作和排错,开发大型项目
渐进式网络应用(PWA)
是谷歌在2015年底提出的概念。基本上算是web应用程序,但在外观和感觉上与原生app
类似。支持PWA
的网站可以提供脱机工作、推送通知和设备硬件访问等功能。
Service Worker
是浏览器在后台独立于网页运行的脚本,它打开了通向不需要网页或用户交互的功能的大门。 现在,它们已包括如推送通知和后台同步等功能。 将来,Service Worker
将会支持如定期同步或地理围栏等其他功能。 本教程讨论的核心功能是拦截和处理网络请求,包括通过程序来管理缓存中的响应。