题目和答案通过网络整理,如有错误欢迎 pr 修正。
题目可能不会按顺序添加,不要直接拉到最后哦,多看几遍吧,温故而知新。
答案被折叠,点击即可查看答案。:heart:
答案
Boolean
Null
Undefined
Number
String
Object
Symbol
(ECMAScript 6 新定义)
(ES6 之前)其中 5 种为基本类型:string
,number
,boolean
,null
,undefined
ES6 出来的 Symbol
也是原始数据类型 ,表示独一无二的值
Object
为引用类型(范围挺大),也包括数组、函数
答案
typeof
typeof 5 // 'number'
typeof '5' // 'string'
typeof undefined // 'undefined'
typeof false // 'boolean'
typeof Symbol() // 'symbol'
console.log(typeof null) //object
console.log(typeof NaN) //number
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
instanceof
通过原型链来判断数据类型的
var p1 = new Person()
p1 instanceof Person // true
Object.prototype.toString.call()
var obj = {}
var arr = []
console.log(Object.prototype.toString.call(obj)) //[object Object]
console.log(Object.prototype.toString.call(arr)) //[object Array]
答案
函数声明表达式
var func = function add(a, b) {
return a + b
}
function 操作符
var func = function(a, b) {
return a + b
}
Function 构造函数
var func = new Function('a', 'b', 'return a + b')
ES6:arrow function
var func = (a, b) => {
return a + b
}
答案
相同点:
在 if
判断语句中,值都默认为 false
大体上两者都是代表无,具体看差异
差异:
null
转为数字类型值为 0,而 undefined
转为数字类型为 NaN
(Not a Number)
undefined
是代表调用一个值而该值却没有赋值,这时候默认则为 undefined
null
是一个很特殊的对象,最为常见的一个用法就是作为参数传入(说明该参数不是对象)
设置为 null
的变量或者对象会被内存收集器回收
答案
let
(声明变量)const
(声明常量,常量不能修改的量)class
(创建类)import/export
(基于 ES6 的模块规范创建导入/导出模块(文件/组件))new Set
(数组去重)Symbol
(唯一的值)var a = Symbol('sunnie')
...ary
(展开运算符、剩余运算符)${}
模板字符串- 解构赋值
let {a} = obj; let [b] = ary
for of
循环()=>{}
箭头函数- 数组新增方法:
flat、find、findIndex
- 对象新增方法:
Object.assign() Object.values() Object.keys() Object.create()
答案
记住关键词:顶部变量属性、变量提升、暂时性死区、重复声明、初始值和作用域然后从这几个方向解释。
声明方式 | 顶部变量属性 | 变量提升 | 暂时性死区 | 重复声明 | 初始值 | 作用域 |
---|---|---|---|---|---|---|
var | 是 | 允许 | 不存在 | 允许 | 不需要 | 除块级 |
let | 不是 | 不允许 | 存在 | 不允许 | 不需要 | 块级 |
const | 不是 | 不允许 | 存在 | 不允许 | 需要 | 块级 |
顶部变量属性: var
声明的变量会挂载在 window 上,而 let
和 const
声明的变量不会
变量提升 : var
变量可在声明之前使用,let
和 const
不可以
暂时性死区:var
不存在暂时性死区,在代码块中,用 let
或 const
声明变量之前,使用会抛出异常 (暂时性死区)
重复声明 : var
允许重复声明,let
和 const
命令声明的变量不允许重复声明
初始值: var
和 let
可以没有初始值,由于 const
声明的是只读的常量,一旦声明,就必须立即初始化,声明之后值不能改变
作用域: var
没有块级作用域,let
和 const
有块级作用域
拓展:修改 const
对象的某个属性会报错吗?
因为对象是引用类型的,const
仅保证指针不发生改变,修改对象的属性不会改变对象的指针,所以是被允许的。
拓展:输出什么?
console.log(a) // undefined
var a = 2
console.log(b) //报错,Uncaught ReferenceError: b is not defined
let b = 1
答案
forEach
for
循环的简化,不能中断,没有break/continue
方法,没有返回值。map
只能遍历数组,不能中断,返回值是修改后的数组。
const arr = [1, 2, 3, 4, 5]
for (let i = 0; i < arr.length; i++) {}
// ES5 forEach
arr.forEach(function(item) {})
// ES5 every
arr.every(function(item) {
return true
})
// ES5 for in 循环的是 key
const object = { name: 'sunnie', age: 18 }
for (let key in object) {
console.log(key)
}
// ES6 for of 循环的是 value
for (let item of object) {
console.log(key)
}
拓展:for...in 迭代和 for...of 有什么区别
for...in 循环出的是 key,for...of 循环出的是 value
答案
let array = [1, 2, 3, 4, 5]
//ES5
let find = array.filter(function(item) {
return item % 2 === 0 //返回满足条件的所有值
})
//ES6
let find = array.find(function(item) {
return item % 2 === 0 //返回满足条件的第一个值
})
console.log(find) // 2
答案
类数组(Array-Like Objects)
是一个类似数组的对象,比如 arguments
对象,还有 DOM API
返回的 NodeList
对象都属于类数组对象。
类数组对象有下面两个特性:
- 具有:指向对象元素的数字索引下标和
length
属性 - 不具有:比如
push
、shift
、forEach
以及indexOf
等数组对象具有的方法
类数组对象转数组方法:
function fn() {
// ES5 方法1:
var arr = Array.prototype.slice.call(arguments)
// ES6 方法1:
let arr = Array.from(arguments)
// ES6 方法2:
let arr = [...arguments]
// 以上三种请任选一种执行测试,为方便写在一起了
arr.push(4) // arr -> [1, 2, 3, 4]
}
fn(1, 2, 3)
答案:star:
- 箭头函数作为匿名函数,不能作为构造函数,不能使用
new
运算符 - 箭头函数不绑定
auguments
,用rest
参数...解决 - 箭头函数会捕获其上下文的
this
值,作为自己的this
值 - 箭头函数当方法使用的时候,没有定义 this 绑定
- 使用
call()
和apply()
调用,传入参数时,参数的改变对this
没有影响 - 箭头函数没有原型属性
- 箭头函数不能当做
Generator
函数,不能使用yiel
关键字。
答案
Javascript 传统方法是通过构造函数定义并生成新对象。
function Parent(name) {
this.name = name
}
Parent.prototype.eat = function() {
console.log('eat')
}
var child = new Parent('parent')
ES6 引入了 CLASS
概念,constructor
方法就是构造函数,定义 类
的方法时,前面不需要加 function
保留字,方法之前不需要逗号。
class Parent {
constructor(name) {
this.name = name
}
eat() {
console.log('eat')
}
}
let child = new Parent('parent')
答案
// ES5 call 继承
function Parent() {
this.name = 'parent'
}
function Child() {
Parent.call(this)
this.type = 'child'
}
缺点:只实现了部分继承 ,父类原型对象 prototype
上的方法无法继承。
// ES5 借助原型链
function Parent() {
this.name = 'parent'
this.play = [1, 2, 3]
}
function Child() {
Parent.call(this)
}
Child.prototype = new Parent()
通过上面的方法继承,尝试修改实例属性
var s1 = new Child()
var s2 = new Child()
s1.play.push(4)
console.log(s1.play, s2.play) // [1,2,3,4],[1,2,3,4]
缺点:实例化两个对象,修改其中一个对象的继承属性,另外也会改变。因为两个实例的原型对象一样。
// ES5 借助Call和原型链
function Parent() {
this.name = 'parent'
this.play = [1, 2, 3]
}
function Child() {
Parent.call(this)
this.type = 'child'
}
Child.prototype = new Parent()
缺点:多执行了一次构造函数会 Child.prototype = new Parent()
function Parent() {
this.name = 'parent'
this.play = [1, 2, 3]
}
function Child() {
Parent.call(this)
this.type = 'child'
}
Child.prototype = Parent.prototype
通过上面的方法继承,尝试修改实例属性
var s1 = new Child()
var s2 = new Child()
console.log(s1.constructor)
缺点:子类实例的构造函数是 Parent,显然这是不对的,应该是 Child
function Parent() {
this.name = 'parent'
this.play = [1, 2, 3]
}
function Child() {
Parent.call(this)
this.type = 'child'
}
Child.prototype = Object.create(Parent.prototype)
答案
添加值方法: Set
使用add
添加,Map
使用set
添加
// Set
let mySet = new Set()
mySet.add(1)
// Map
const myMap = new Map()
const o = { p: 'hello' }
myMap.set(o, 'world')
答案
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
- 静态方法调用直接在类上进行,而在类的实例上不可被调用。
- 父类的静态方法,可以被子类继承。
class Foo {
static classMethod() {
return 'hello'
}
}
// 静态方法调用直接在类上进行,而在类的实例上不可被调用。
Foo.classMethod() // 'hello'
var foo = new Foo()
foo.classMethod() // TypeError: foo.classMethod is not a function
// 父类的静态方法,可以被子类继承。
class Bar extends Foo {}
Bar.classMethod() // 'hello'
答案:star:
闭包是指有权访问另一个函数作用域内变量的函数。
闭包有两个常用的用途。
- 可以读取函数内部的变量
- 让这些变量的值始终保持在内存中,不会在调用后被自动清除
知识点:rest参数
类数组
答案
// ES5
function sum() {
console.log(arguments)
}
// ES6
function sum(...rest) {
// rest 是数组
console.log(rest)
}
sum(1, 2, 3)
知识点:sort
箭头函数
答案
const arr = [10, 5, 40, 25, 1000, 1]
arr.sort((a, b) => {
return a - b
})
console.log(arr) // [1, 5, 10, 25, 40, 1000]
知识点:合并数组
答案
let a = [1, 2, 3]
let b = [4, 5, 6]
// ES5 方法一
a.concat(b)
// ES5 方法二
Array.prototype.push.apply(a, b)
console.log(a) // [1,2,3,4,5,6]
// ES6
let c = [...a, ...b]
console.log(c)
答案:star:
浅拷贝
只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
深拷贝
会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
如何实现浅拷贝:
function clone(target) {
let cloneTarget = {}
for (const key in target) {
cloneTarget[key] = target[key]
}
return cloneTarget
}
如何实现深拷贝:
如果你的对象没有复杂类型的数据如 Dates
, functions
, undefined
, Infinity
, RegExps
, Maps
, Sets
, Blobs
等 简单的实现方法:
JSON.parse(JSON.stringify())
基础款:
function clone(target) {
if (typeof target === 'object') {
let cloneTarget = {}
for (const key in target) {
cloneTarget[key] = clone(target[key])
}
return cloneTarget
} else {
return target
}
}
拷贝的时候不能只考虑数组,还有其他类型,以及考虑性能,这里直接来看大神如何一步一步实现:
如何写出一个惊艳面试官的深拷贝?
ES6 Object.assign()
是深拷贝还是浅拷贝?
Object.assign
方法实行的是浅拷贝,不是深拷贝。
也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
注意:object 只有一层的时候,是深拷贝
let obj = {
username: 'sunnie',
}
let obj2 = Object.assign({}, obj)
obj2.username = 'change' // `深拷贝`修改新对象不会改到原对象
console.log(obj) // {username: "sunnie"}
答案
- 在浏览器里,在全局范围内
this
指向window
对象; - 在函数中,this 永远指向最后调用他的那个对象;
- 构造函数中,this 指向 new 出来的那个新的对象;
- call、apply、bind 中的 this 被强绑定在指定的那个对象上;
- 箭头函数中 this 比较特殊,箭头函数 this 为父作用域的 this,不是调用时的 this.要知道前四种方式,都是调用时确定,也就是动态的,而箭头函数的 this 指向是静态的,声明的时候就确定了下来; apply、call、bind 都是 js 给函数内置的一些 API,调用他们可以为函数指定 this 的执行,同时也可以传参。
答案:star:
apply
、call
、bind
都是可以改变 this
的指向的,但是这三个函数稍有不同。
先说 apply 和 call 的区别
call()
方法接受的是参数列表,而apply()
方法接受的是一个参数数组。
var person = {
value: 1,
}
function say(name, age) {
console.log(name)
console.log(age)
console.log(this)
}
// call 用法
say.call(person, 'sunnie', 18)
// apply 用法
say.apply(person, ['sunnie', 18])
// kevin
// 18
// {value: 1}
看完之后还是很疑惑为什么会有这个玩意??
说改变this
的指向太抽象。我们这样理解:有一个对象 person
,懒得给它新定义 say
方法,
就通过 call
或 apply
,用 say.call(person)
给 person
添加一个 say
方法,并执行它。
这个时候this
从原来的 window
改成了 person
。
call 实现
思路:1.将函数设为对象的属性 2.执行该函数 3.删除该函数
// 第一版
Function.prototype.call2 = function(context) {
// 首先要获取调用 call 的函数,用this可以获取
context.fn = this
context.fn()
delete context.fn
}
// 测试一下
var foo = {
value: 1,
}
function bar() {
console.log(this.value)
}
bar.call2(foo) // 1
apply 实现
apply
与 call
类似,直接去看大神怎么一步一步实现
JavaScript 深入之 call 和 apply 的模拟实现
bind 与 apply 、call 的区别
bind
返回值为一个修改完 this
指向的函数,需要主动调用
// 第一版
Function.prototype.bind2 = function(context) {
var self = this
return function() {
self.apply(context)
}
}
这里直接来看大神如何一步一步实现:
JavaScript 深入之 call 和 apply 的模拟实现
答案
函数防抖
(debounce):防抖就是将一段时间内连续的多次触发转化为一次触发。
比如:我点击一个按钮,delay
设置 3s ,我手速超快,从来不让点击间隔时间大于等于 3s 函数就不执行,一旦大于等于了 3s 就执行了
function debounce(func, delay) {
let timeout
return function() {
clearTimeout(timeout) // 如果持续触发,那么就清除定时器,定时器的回调就不会执行。
timeout = setTimeout(() => {
func.apply(this, arguments)
}, delay)
}
}
函数节流
(throttle):节流顾名思义则是将减少一段时间内触发的频率。
比如:我点击一个按钮,threshhold
设置 3s, 当我点第一次点击执行函数,接下来的 3s 内点都少次都没用,直到距离第一次 3s 执行第二次
function throttle(fn, threshhold = 3000) {
let last
let timer
return function() {
const now = +new Date()
if (last && now < last + threshhold) {
clearTimeout(timer)
timer = setTimeout(() => {
last = now
fn.apply(this, arguments)
}, threshhold)
} else {
last = now
fn.apply(this, arguments)
}
}
}
应用场景
mouse move 时减少计算次数:debounce
input 中输入文字自动发送 ajax 请求进行自动补全: debounce
ajax 请求合并,不希望短时间内大量的请求被重复发送:debounce
resize window 重新计算样式或布局:debounce
或 throttle
scroll 时触发操作,如随动效果:throttle
对用户输入的验证,不想停止输入再进行验证,而是每 n 秒进行验证:throttle
答案
答案
通过图片观察
相同:
- 加
defer
和async
后,script
读取和html
解析同时进行,不会阻塞html
解析。
不同: defer
会在html
解析完之后执行 ,async
则是异步加载完script
后立即执行 。- 图中未表现出
defer
是按照加载顺序执行脚本的。async
则是一个乱序执行,反正对它来说脚本的加载和执行是紧紧挨着的,所以不管你声明的顺序如何,只要它加载完了就会立刻执行。
知识点:对象
答案
// 第一种方式: 字面量
var o1 = { name: 'o1' }
var o2 = new Object({ name: 'o2' })
// 第二种 构造函数
var M = function(name) {
this.name = name
}
var o3 = new M('o3')
// 第二种 Object.create
var p = { name: 'o4' }
var o4 = Object.create(p)
知识点:原型链
答案
// 在声明一个函数,js 引擎会为该函数添加 prototype
function M() {}
// M 的原型对象 prototype constructor 指向 M 构造函数本身
M.prototype.constructor === M // true
// M 被 new 称为 实例,o 为实例
var o = new M()
// 实例 m 有个__proto__ 属性 指向 原型对象
o.__proto__ === M.prototype
在声明一个函数,js 引擎会为该函数添加 prototype
属性,被称为原型对象
,对象没有 prototype
属性
原型对象
有个 constructor
指向 构造函数
任何一个函数通过被 new
称为实例
,这个函数称为 构造函数
实例
有个 __proto__
属性 指向 原型对象
如果实例
上没有找到属性或方法,则会向它的原型对象
上找,直到找到 Object.prototype
还没有就返回 没有定义
答案
答案
JavaScript 是一门单线程的语言,他的异步是通过 Event Loop 事件循环机制来实现的。
大体有三个部分组成:
- 调用栈(Call Stack)
- 消息队列(Message Queue)也叫任务队列(Task Queue)、回调队列(Callback Queue)里面的任务称为:宏任务(Macrotask)
- 微任务队列(Microtask Queue)。
Event Loop 开始时,会从全局栈开始一行一行执行,遇到函数执行会压入调用栈中。当函数返回后,会从调用栈中弹出。
如果有 fetch
,事件回调
,settimeout
,setInterval
,则会将它的回调函数会入队到消息队列中,在调用栈清空的时候执行。
使用 promise
,async\await
创建的异步操作,会加入到微任务队列中,会在调用栈清空的时候立即执行,并且处理期间新加入的微任务也会一同执行。
promise.then
的回调函数会入队到 微任务队列中。
Microtask (微任务)
- process.nextTick
- promise
- MutationObserver
Macrotask (宏任务)
- setTimeout
- setImmediate
- setInterval
- I/O
- UI 渲染
答案
0.1和0.2在转换成二进制后会无限循环,由于标准位数的限制后面多余的位数会被截掉,此时就已经出现了精度的损失,相加后因浮点数小数位的限制而截断的二进制数字在转换为十进制就会变成0.30000000000000004。
答案
// parseInt 只保留整数部分
parseInt(5.1234) // 5
// 向下取整
Math.floor(5.1234) // 5
// 向上取整
Math.ceil(5.1234) // 6
// 四舍五入
Math.round(5.1234) // 5
Math.round(5.6789) // 6
// 按照位数四舍五入
var num = new Number(5.6789)
num.toFixed(2) // 5.68
// 取绝对值
Math.abs(-1) // 1
答案
模块化 | 元素能否点击 | 元素是否消失 | 性能 | 继承 |
---|---|---|---|---|
CommonJS | 非继承属性 | |||
AMD | ||||
CMD |
答案
- 1、创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
- 2、属性和方法被加入到 this 引用的对象中。
- 3、新创建的对象由 this 所引用,并且最后隐式的返回 this 。