- Notifications
You must be signed in to change notification settings - Fork8
前端面试题 | Javascript 题 | CSS 题 | 前端届的3年高考5年模拟
sunniejs/javascript-question
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
题目和答案通过网络整理,如有错误欢迎 pr 修正。
题目可能不会按顺序添加,不要直接拉到最后哦,多看几遍吧,温故而知新。
答案被折叠,点击即可查看答案。:heart:
答案
BooleanNullUndefinedNumberStringObjectSymbol (ECMAScript 6 新定义)
(ES6 之前)其中 5 种为基本类型:string,number,boolean,null,undefined
ES6 出来的Symbol 也是原始数据类型 ,表示独一无二的值Object 为引用类型(范围挺大),也包括数组、函数
答案
函数声明表达式
varfunc=functionadd(a,b){returna+b}
function 操作符
varfunc=function(a,b){returna+b}
Function 构造函数
varfunc=newFunction('a','b','return a + b')
ES6:arrow function
varfunc=(a,b)=>{returna+b}
答案
相同点:
在if 判断语句中,值都默认为false
大体上两者都是代表无,具体看差异
差异:
null 转为数字类型值为 0,而undefined 转为数字类型为NaN(Not a Number)undefined 是代表调用一个值而该值却没有赋值,这时候默认则为undefinednull 是一个很特殊的对象,最为常见的一个用法就是作为参数传入(说明该参数不是对象)
设置为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)// undefinedvara=2console.log(b)//报错,Uncaught ReferenceError: b is not definedletb=1
答案
forEachfor循环的简化,不能中断,没有break/continue方法,没有返回值。map只能遍历数组,不能中断,返回值是修改后的数组。
constarr=[1,2,3,4,5]for(leti=0;i<arr.length;i++){}// ES5 forEacharr.forEach(function(item){})// ES5 everyarr.every(function(item){returntrue})// ES5 for in 循环的是 keyconstobject={name:'sunnie',age:18}for(letkeyinobject){console.log(key)}
// ES6 for of 循环的是 valuefor(letitemofobject){console.log(key)}
拓展:for...in 迭代和 for...of 有什么区别
for...in 循环出的是 key,for...of 循环出的是 value
答案
答案
类数组(Array-Like Objects) 是一个类似数组的对象,比如arguments 对象,还有DOM API 返回的NodeList 对象都属于类数组对象。
类数组对象有下面两个特性:
- 具有:指向对象元素的数字索引下标和
length属性 - 不具有:比如
push、shift、forEach以及indexOf等数组对象具有的方法
类数组对象转数组方法:
functionfn(){// ES5 方法1:vararr=Array.prototype.slice.call(arguments)// ES6 方法1:letarr=Array.from(arguments)// ES6 方法2:letarr=[...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 传统方法是通过构造函数定义并生成新对象。
functionParent(name){this.name=name}Parent.prototype.eat=function(){console.log('eat')}varchild=newChild('parent')
ES6 引入了CLASS 概念,constructor方法就是构造函数,定义类 的方法时,前面不需要加function 保留字,方法之前不需要逗号。
classParent{constructor(name){this.name=name}eat(){console.log('eat')}}letchild=newChild('parent')
答案
// ES5 call 继承functionParent(){this.name='parent'}functionChild(){Parent.call(this)this.type='child'}
缺点:只实现了部分继承 ,父类原型对象prototype 上的方法无法继承。
// ES5 借助原型链functionParent(){this.name='parent'this.play=[1,2,3]}functionChild(){Parent.call(this)}Child.prototype=newParent()
通过上面的方法继承,尝试修改实例属性
vars1=newChild()vars2=newChild()s1.play.push(4)console.log(s1.play,s2.play)// [1,2,3,4],[1,2,3,4]
缺点:实例化两个对象,修改其中一个对象的继承属性,另外也会改变。因为两个实例的原型对象一样。
// ES5 借助Call和原型链functionParent(){this.name='parent'this.play=[1,2,3]}functionChild(){Parent.call(this)this.type='child'}Child.prototype=newParent()
缺点:多执行了一次构造函数会Child3.prototype = new Parent()
functionParent(){this.name='parent'this.play=[1,2,3]}functionChild(){Parent.call(this)this.type='child'}Child.prototype=Parent.prototype
缺点:多执行了一次构造函数会Child3.prototype = new Parent()
// 只实现了部分继承 ,prototype上的没有被继承functionAnimal(type){this.type=type}functionDog(){Animal.call(this)}
// ES6 实现继承classAnimal{construtor(type){this.type=type}eat(){console.log('eat')}}classDogextendsAnimal{construtor(type){supper(type)}}
答案
添加值方法:Set 使用add添加,Map 使用set添加
// SetletmySet=newSet()mySet.add(1)// MapconstmyMap=newMap()consto={p:'hello'}myMap.set(o,'world')
答案
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
- 静态方法调用直接在类上进行,而在类的实例上不可被调用。
- 父类的静态方法,可以被子类继承。
classFoo{staticclassMethod(){return'hello'}}// 静态方法调用直接在类上进行,而在类的实例上不可被调用。Foo.classMethod()// 'hello'varfoo=newFoo()foo.classMethod()// TypeError: foo.classMethod is not a function// 父类的静态方法,可以被子类继承。classBarextendsFoo{}Bar.classMethod()// 'hello'
答案:star:
知识点:rest参数类数组
答案
// ES5functionsum(){console.log(arguments)}// ES6functionsum(...rest){// rest 是数组console.log(rest)}sum(1,2,3)
知识点:sort箭头函数
答案
constarr=[10,5,40,25,1000,1]arr.sort((a,b)=>{returna-b})console.log(arr)// [1, 5, 10, 25, 40, 1000]
知识点:Object
答案
答案:star:
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
如何实现浅拷贝:
functionclone(target){letcloneTarget={}for(constkeyintarget){cloneTarget[key]=target[key]}returncloneTarget}
如何实现深拷贝:
如果你的对象没有复杂类型的数据如Dates,functions,undefined,Infinity,RegExps,Maps,Sets,Blobs等 简单的实现方法:
JSON.parse(JSON.stringify())
基础款:
functionclone(target){if(typeoftarget==='object'){letcloneTarget={}for(constkeyintarget){cloneTarget[key]=clone(target[key])}returncloneTarget}else{returntarget}}
拷贝的时候不能只考虑数组,还有其他类型,以及考虑性能,这里直接来看大神如何一步一步实现:
如何写出一个惊艳面试官的深拷贝?
ES6Object.assign() 是深拷贝还是浅拷贝?
Object.assign方法实行的是浅拷贝,不是深拷贝。
也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
注意:object 只有一层的时候,是深拷贝
letobj={username:'sunnie',}letobj2=Object.assign({},obj)obj2.username='change'// `深拷贝`修改新对象不会改到原对象console.log(obj)// {username: "sunnie"}
答案
答案:star:
apply 、call、bind 都是可以改变this 的指向的,但是这三个函数稍有不同。
先说 apply 和 call 的区别
call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
varperson={value:1,}functionsay(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=thiscontext.fn()deletecontext.fn}
apply 实现
apply 与call 类似,直接去看大神怎么一步一步实现
JavaScript 深入之 call 和 apply 的模拟实现
bind 与 apply 、call 的区别
bind 返回值为一个修改完this 指向的函数,需要主动调用
// 第一版Function.prototype.bind2=function(context){varself=thisreturnfunction(){self.apply(context)}}
这里直接来看大神如何一步一步实现:
JavaScript 深入之 call 和 apply 的模拟实现
答案
函数防抖(debounce):防抖就是将一段时间内连续的多次触发转化为一次触发。
比如:我点击一个按钮,delay设置 3s ,我手速超快,从来不让点击间隔时间大于 3s 函数就不执行,一旦大于了 3s 就执行了
functiondebounce(func,delay){lettimeoutreturnfunction(){clearTimeout(timeout)// 如果持续触发,那么就清除定时器,定时器的回调就不会执行。timeout=setTimeout(()=>{func.apply(this,arguments)},delay)}}
函数节流(throttle):节流顾名思义则是将减少一段时间内触发的频率。
比如:我点击一个按钮,threshhold设置 3s, 当我点第一次点击执行函数,接下来的 3s 内点都少次都没用,直到距离第一次 3s 执行第二次
functionthrottle(fn,threshhold=3000){letlastlettimerreturnfunction(){constnow=+newDate()if(last&&now<last+threshhold){clearTimeout(timer)timer=setTimeout(()=>{last=nowfn.apply(this,arguments)},threshhold)}else{last=nowfn.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则是一个乱序执行,反正对它来说脚本的加载和执行是紧紧挨着的,所以不管你声明的顺序如何,只要它加载完了就会立刻执行。
About
前端面试题 | Javascript 题 | CSS 题 | 前端届的3年高考5年模拟
Topics
Resources
Uh oh!
There was an error while loading.Please reload this page.

