1.javascript知识简介
一般来说,对于js的考察核心在于,在你对你自己的知识有信心的时候。你要预判一下通常面试官会问你什么。这样才能做到面试的时候,可以自信的回答面试遇到的问题。
原理篇
对于js有几个核心的概念,需要掌握,面试通常会问到的几个比如执行上下文,原型,作用域,闭包
执行上下文:
js执行代码的时候,所包含的执行环境和变量
-
全局执行上下文
- this的值 指向window
- 词法环境
- 变量对象
-
函数上下文
-
this的值 根据具体的情况分析
-
默认绑定
绑定window
- 显示绑定
call apply,bind
function test(){ console.log('test') } let obj={} te- 隐式绑定
function foo(){ console.log(this.a) } let obj={a:'1', foo:foo} obj.fool()- 箭头函数绑定
箭头函数不绑定this的值,this想当于普通变量
- new绑定 > 会将this绑定到返回的实例对象上
- 词法环境
- 变量对象
-
-
eval上下文
代码执行过程:
1 .创建全局执行上下文 2. 执行全局上下文,遇到函数,创建函数执行上下文 3.执行函数上下文 4.执行结束,全局上下文继续执行
作用域:
变量和声明的作用范围
- 块级作用域
- 函数作用域 作用域链:
原型
- 构造函数
可以通过new来创建对象的函数
function Person(name){
this.name=name
}
let p =new Person('kangkai')
symbol是构造函数么,因为symbol不支持new语法,所以不算是
- 原型
可以用于继承方法和属性的对象 ,javascript是基于原型的语言,每一个对象都拥有一个原型,挂载在构造函数的prototype
Person.prototyep==p.__proto__
- 原型链
每个对象都一个原型,通过__proto__指向原型对象,原型对象还有原型,通过这样一层层,最终原型链的顶端是null,这个就叫原型链
p.__proto__==Person.prototype
p.__proto__.__proto__=Object,prototype
p.__proto__.__proto__.__proto__=null
- 属性查找机制: 根据原型链一步一步查找属性的机制,先查找对象本身,在查找原型对象,沿着原型链一直到原型对象为null
- 属性修改机制: 只会修改对象本身的属性
闭包
可以访问上层函数的变量的函数
函数科里化
在一个函数的里面,填充部分函数,然后返回一个新函数的过程叫做函数科里化
let add=function(a){
return function(b){
return a+b
}
add(1)(2)
继承
原型链继承
将子类的原型对象,指向父类实例
function Father(){}
function Children(){}
Children.prototype=new Father()
构造继承
通过call改变子类的this的值
function Children(){
Father.call(this)
}
实例继承
function Children(){
let instance=new Father()
return instacne
}
拷贝继承
将父类的属性和方法拷贝到子类实例
组合继承
调用父类的构造,并且将父类的实例作为子类的原型返回
function Children(){
Father.call(this)
}
Children.prototype=new Father()
Children.prototype.constructor=Children
组合继承
es6 extends
class Father(){
constructor(){}
}
class Children extends Father{
constructor(){
super()
}
}
基础数据结构
- 数组 map:遍历数组返回新数组
[1,2,3].map(x=>x+1) //[2,3,4]
- 栈
function Stack(){
this.data=[]
}
Stack.prptype.push=function(data){
this.data.push(data)
}
Stack.proptype.pop=function(){
this.data.pop()
}
3.队列
function Queue(){
this.data=[]
}
Queue.prototype.push=function(data){
this.data.push(data)
}
4.链表
function Node(data){
this.data=data
this.next=null
}
function List(){
this.head=null
}
5.树
function Node(data,left,right){
this.data=data
this.left=left
this.right=right
}
function Tree(){
this.root=null
}
手写代码
es5实现let,const
- let
for(let i=0;i<5;i++){
console.log(i)
}
babel实现方式
for(let _i=0;_i<5;_i++){
console.log(_i)
}
自执行函数方式
(function(){
for(var i=0;i<5;i++){
console.log(i)
}
})()
- const
function _const(key,value){
const desc={
value,
writeable:false
}
Object.defineProperty(window,key,desc)
}
call apply bind的手写
call,apply 和bind都是用来改变this的指向,将this的值指向传入的对象,call和apply的区别在于参数,参数是多个和apply的参数是列表,bind的区别在于,bind返回的是一个函数,call和apply实际上都是立刻执行的函数
- 获取this的值
- 获取参数的值
- 返回值
- 边界处理
//call编写
Function.prototype.myCall()=function (context) {
context=context?Object(context):window
context.fn=this
let args=arguments.slice(1)
let result=context.fn(...args)
delete context.fn
return result
}
//apply编写
Function.prototype.myApply = function (context, paramter) {
context = context ? Object(context) : window
context.fn = this
let result
if (!paramter) {
result = context.fn()
} else {
result = context.fn(...paramter)
}
delete context.fn
return result
}
//bind 编写
Function.prototype.myBind = function (context) {
let self = this
let args = Array.prototype.slice.call(arguments, 1)
let fNop = function () {}
var fBound = function () {
return function () {
let bindArgs = Array.prototype.slice.call(arguments)
self.apply(context, args.concat(bindArgs))
}
}
fNop.prototype = fBound.prototype
fBound.prototype = new fNop()
return fBound
}
new的手写
new是构建一个实例,首先要知道new的过程中做了什么 1.创建一个对象实例 2.原型绑定 3.this指向当前实例 3.返回实例
function create(){
Con=[].shift.call(arguments)//获取构造函数,并删除argumnets的第一个对象
let obj=Object.create(Con.prototype)//创建空对象并连接原型
let ret=Con.apply(obj,arguments)//绑定this的值
return ret instanceof Object? ret:obj
}
节流防抖的手写
- 节流
function throttle (fn, delay) {
let flag = true
let timer = null
return function (...args) {
let context = this
if (!flag) return
flag = false
clearTimeout(timer)
timer = setTimeout(function () {
fn.apply(context.args)
flag = true
}, delay)
}
}
- 防抖 简单的来说是在某个时间段内,多次触发,只执行最后一次
debounce: function (fn, delay) {
let timer = null
return function (...args) {
let context = this
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(context, args)
}, delay)
}
},
发布订阅的手写
function Subject() {
this.observers=[]
}
Subject.prototype={
subscribe(observer){
this.observers.push(observer)
}
unsubscribe(observerRemove){
this.observers=this.observers.filter(observer=>{
return observer!==observerRemove
})
},
broadcast(){
this.observers.forEach(obsever=>{
obsever.call()
})
}
浅拷贝和深拷贝的手写
- 浅拷贝 对于浅拷贝来说,核心是只需要拷贝属性即可,不需要递归到深层次去拷贝
///考虑边界条件
function shallowCopy(source){
let target={}
for(let key in source){
if(Object.prototype.hasOwnProperty.call(source,key)){
target[key]=source[key]
}
}
return target
}
- 深拷贝 递归实现
promise的手写
手写instanceof
function my_instance_of (leftVaule, rightVaule) {
if (typeof leftVaule !== 'object' || leftVaule === null) return false
let rightProto = rightVaule.prototype,
leftProto = leftVaule.__proto__
while (true) {
if (leftProto === null) {
return false
}
if (leftProto === rightProto) {
return true
}
leftProto = leftProto.__proto__
}
}
手写事件委托
ul.addEventListener('click',e=>{
console.log(e,e.target)
if(e.target.tagName.toLowerCase()==='li'){
console.log('print')
}
})
function delegate (element, eventType, fn, seletor) {
seletor.addEventListener(eventType, e => {
let el = e.target
while (!el.match(seletor)) {
if (element === el) {
el = null
break
}
el = el.parentNode
}
el && fn.call(el, e, el)
}, true)
return element
}
数组去重
let res=array.filter((items,index,array)=>{
return array.indexOf(items)===index
})
return res
let unique_3 = arr => arr.reduce((pre, cur) => pre.includes(cur) ? pre : [...pre, cur], []);
let obj={}
return array.filter((item,index,array)=>{
return obj.hasOwnProperty(typeof item+item)?false:(obj [typeof item+item]=true))
})
实现科里化
let curring=(fn,...args)=>{
fn.length>args.length?(...arguments)=> curring(fn ...args,...arguments):fn(...args)
}
数组flat
let arrMap=(array)=>{
return array.reduce((res,cur)=>{
if(Array.isArray(cur)){
return [...res,arrMap(cur)]
}else{
return [...res,cur]
}
},[])
}
function arrDep(arr,deep) {
return d>0?arr.reduce((acc,val)=>acc.contact(Array.isArray(val)?arrDep(val,deep-1):val))
}
实现sleep
function sleep(time){
return new Promise((resolve)=>{
setTimeout(()=>{},time)
}
}
实现promise.all 和promise.race
Promise.all = function (arr) {
return new Promise((resolve, reject) => {
if (arr.length === 0) {
return resolve([])
} else {
let res = []
let count = 0
for (let i = 0; i < arr.length; i++) {
if (!(arr[i] instanceof Promise)) {
res[i] = arr[i]
if (++count === arr.length) {
resolve(res)
}
} else {
arr[i].then(data => {
res[i] = data
if (++count === arr.length) {
resolve(res)
}
}, error => {
reject(error)
})
}
}
}
})
}
es6篇
解构赋值
数组解构赋值
let [a,b]=[1,2]
let [a=1,b]=[3]
对象的解构赋值
let {foo,bar}={foo:'foo',bar:'bar'}
const {log}=console
字符串的解构赋值
ler [a,b,c]='hello'
数组的扩展
扩展运算符 ...
[].push(...[1,2,3,4])
Array.from :将类数组对象转换换成数组
let obj={ a:1,b:2,c:3}
Array.from(obj)//[1,2,3]
Array.from(obj,x=>x*2)//对每个元素进行处理,并返回
Array.of:将一组值转换为数组
Array.of(1,2,3)//[1,2,3]
includes:
[1,2,3].includes(3)
flat:拉平数组 参数拉平的层数 参数INfinity 无论多少层都拉平
[1,2,[3,4],[5]].flat()//[1,2,3,4,5]
[1,2,[3,4],[5,[6,7]]].flat(2)//[1,2,3,4,5,6,7]
对象扩展
Object.is 判断两个对象是否相等 严格相等 Nan==Nan +0!=-0
Object.is({},{}) //false
object.is(+0,-0) //false
Object.assign 合并对象
let b={}
let a={a:'hello'}
Object.assugn(b,a)
console.log(b)
Object.getOwnPropertyDescriptor Object.getPrototypeof
proxy
symbol
es6新增的数据类型,代表独一无二的值 symbol('a') 不能使用new
set,map
set不重复的元素集合 map键值对,键值可以是任何类型
- weakMap 结构与map类似,区别是键值只接受对象,可以避免内存泄漏,直接被垃圾回收
- weakSet
promise原理
封装异步操作
promise.all
将多个promise实例包装成一个promise实例
Promise.all([promsie1,promise2])
参数是一个包含若干promise实例的数组,如果数组里不是promise,则会用Promise.resolve方法包装,每个promise都fullfilled,外部promise才会fullfilled,触发回调函数,如果有一个rejected,则promise变成rejected
promise.race
async和await
async return语句会返回一个promise对象
async function(){
await setTimeout({},1000)
export&import
- export 导出 export default 匿名导出
- import 导入
let const
let,const都是块级作用域
{ let a=1}
console.log(a)
for(var i=0;i<10;i++){
a[i]=function(){
console.log(i)
}
}
a[6]()//10
for(let i=0;i<10;i++){
let i ='abc'
console.log(i)
}//'abc' 'abc' 'abc'
for循环的父作用域和子作用域可以声明相同的变量
const的值是不可以改变的
const a=1
a=2//错误
const b=[]
b.push(1)
var 声明的变量会出现变量提升的现象
a=1
var a
暂时性死区
for(var i=1;i<5;i++){
console.log(i)
}
//此时i输出的值是多少
class 类
class person{
construct(name){
this.name=name
}
箭头函数
function hello(a,b){
return a+b
}
(a,b)=>a+b
function test(){
console.log('hello')
}
()=>{console.log('hello'}
this的指向不在是函数本身 不能使用new来作为构造函数
面试经典题
用js实现一个无限循环的动画 1.用setTimeout实现
let e =document.getElementsByClassName('e')
let left=0
let flag=true
setInterval(()=>{
left==0?flag=true:left==100?flag=false:''
flag?e.style.left=` ${left++}px`:e.style.left=`${left--}px`
},1000/60)
2.requestAnimateFrame
//浏览器兼容性处理
window.requestAnimationFrame=function () {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback,1000/60)
}
}()
let e=document.getElementsByClassName('e')
let flag=true
let left=0
function render() {
left==0?flag=true:left==100?flag=false:''
flag?e.style.left=`${left++}px`:e.style.left=`${left--}px`
}
(function animloop() {
render()
window.requestAnimationFrame(animloop)
})()
面试高频考点
- 1.typeof
对于引用类型的除了function,其他都是判断是object 判断null也是object ,undefined的类型是undefined
-
2.object 键值对,键必须是字符串,如果不是字符串会调用tostring方法进行转换, 如果object会转换成 object object
-
3.tosting转换
- 转换成字符串 字符串连接符+ 一边是字符串
- 转换成number 关系运算符> ,<+(有一边是数字)
- 转换成bool值 逻辑非运算符会转换成bool值
2.vue框架
一般面试前端的技术栈最起码会有vue和react其中的一个作为主要掌握的框架
mvvm模式
model-view-viewmodel
什么是vue
vue是一套渐进式构建前台界面的javascript框架。vue的特点包含组件系统,声明式渲染,大规模式路由
vue的生命周期
vue一般会经过四个阶段
-
创建阶段(beforeCreated/created)
beforecreated:实例刚刚创建,data等选项还未加载
created:实例属性加载完成,dom还未创建
-
挂载阶段(beforeMounted/mounted)
beforeMounted:template元素已经编译好,但是尚未挂载
mountede:模板已经挂载,生成真实dom元素
-
更新阶段(beforeUpdated/updated)
beforeUpdated:执行diff语法,比较UI是否需要更新
updated:组件更新结束
-
销毁阶段(beforedestroyed/destroyed)
beforeDestroyed:销毁开始
destroyed:销毁结束
vue是怎么渲染界面的
vue对于template的编译,是先将template模板编译成为AST,抽象语法树,在通过generate函数生成render函数,最后生成VNOe,在通过渲染成dom
AST
** 抽象语法树**将代码转换成树状结构
babel编译原理
1.将es6代码解析成AST 2.对AST进行遍历转译得到新的AST 3.新的AST转换成ES5
vue是如何实现数据劫持的
vue利用的是object.definePropoty
手写实现
<div id="app">
<input type="text" id='txt'>
<p id="show-txt"></p>
</div>
let obj={}
Object.defineProperty(obj,'txt',{
set:function (newValue){
document.getElementById('txt').value=newValue
document.getElementById('show-txt').innerHTML=newValue
},get:function () {
return obj
}
})
document.addEventListener('keyup',function (e) {
obj.txt=e.target.value
})
}
vue虚拟dom
建设dom 树diff 同层对比,输出patch 更新dom 遍历patch,局部更新dom
function diff(oldTree,newTree){
let patch={}
DFS(oldTree,newTree,0,patch)
return patch
}
function DFS(oldTree,newTree,deep,patch){
}
vuenexttick更新
在一次事件内,多次触发更新dom,vue是不会立刻更新dom的,而是通过将更新动作上发到异步队列中,一次性的更新dom,如果需要实时触发,利用this.$nexttcik
vue列表为什么要有key
key对于列表的作用来说,是就地复用,如果没有key值,则当同一节点来,key的意义在于排序和重复使用,diff算法的时候,快速找到节点
3.算法
对于大多数前端,实际上涉及的算法其实并不算很多,但是面试的时候会多多少少都会遇到,对于这些基础的算法,多少需要掌握
数据结构
字符串
字符串常用操作 indexof lastIndexof charat split replace
-
字符串的常见面试题
- 字符串正则替换
1.给定一个字符串 'hello jhello hello',将空格替换成20%
let s='hello jhello hello' s.replace(/\s/g,'20%')2.字符串统计出现最多的字符 思路:哈希表,建立字符和count的统计 3.字符串逆序 思路转换成数组,在拼成字符串 4. 字符串全排列: 给定几个字母,排成的全部排列的组合是多少 思路:回溯算法 回溯算法的框架
数组
数组常用操作 push pop shift pop unshift splice
链表
function Node(data){
this.data=data
thi.next=null
}
function LinkedList(){
this.head=null
}
栈
栈的特点:先进后出
function stack(){
this.value=[]
}
stack.propotype.push=function(val){
this.value.push(val)
}
stack.propotype.pop=function(){
this.value.shift()
}
队列
树
function Node(data,left,right){
this.data=data
this.left=left
this.right=right
}
function Tree(root){
this.root=root
}
排序
冒泡算法 基础
两两比较,较大交换到前面去
function bubuleSort(arr){
for(let i=0;i<arr.length;i++){
let complete=true
for(let j=i+1;j<arr.length;j++){
if(arr[i]<arr[j]){
[arr[i],arr[j]]=[arr[j],arr[i]]
}
}
if(complete){
break
}
插入排序 基础
将左边的部分视为已经排列好的,比较右边的部分然后,一次比较插入
归并排序 基础
取最左边的值,然后将数组组成比当前值左边大右边小的状态,然后在递归进行左边和右边的部分
选择排序 基础
每一次循环,找到最小的值,
快速排序 基础
回溯算法
回溯法算术框架:定义最后需要的排列组合结果 定义当前路径的和 定义
双指针
双指针算术框架: 定义遍历的左右指针
贪心算法
动态规划
我不会
DFS BFS
-
深度优先遍历 DFS
-
广度优先遍历 BFS
4.浏览器知识
浏览器绘制界面原理
以googlev8引擎为例,浏览器解析html生dom树,解析css生成css规则树,html和css合成渲染树,渲染树通过调用api布局绘制成为界面。
javascript的加载会影响dom的解析么
javascript的加载和解析会阻塞dom树的生成和解析,遇到js代码后,会将控制权交给javascript引擎,等js解析和执行完后,控制权才会重新给回dom解析
标签 dfer和async的区别
defer 延迟加载,元素解析完成后执行脚本 async 异步加载,会阻塞元素渲染
什么是重绘和回流
- 重绘 渲染树的元素的样式发生了改变,但是布局并没有发生改变,比如background_color dom发生改变的时候
- 回流 渲染树的元素尺寸布局隐藏与否发生的了改变,大小高度改变
如何减少重绘和回流
- 使用transform替代top
- 使用visiblity替代display:none
- 减少使用table布局
- 在for循环中不要多次改变样式
浏览器事件循环
在JavaScript中,任务分为两种一种Task宏任务 ,另外一种microTask微任务
- 宏任务 setTimeout,setInterval I/O
- 微任务 Process.nextTick,Promise
先执行宏任务,在清空微任务
浏览器缓存
强缓存和协商缓存,什么是强缓存,什么是协商缓存 根据什么来标识的,强缓存是强制明智能 http头cache-control和expire来标识http请求的属性
强缓存
- cache-contol Max-age是标明过期的时间
- expire 是标明过期的具体时间,由于客户端和服务端的时间不一致,建议使用cache-contorl,同时拥有两个属性的时候,cache-control会覆盖
第一次请求的时候保存缓存到浏览器里面,第二次通过http头部属性的cache-control和expire属性,判断是否缓存是否过期,不过期即命中缓存。过期便要向服务器发送请求数据是否有变化,如果有变化,则要更新缓存数据
协商缓存
当判断缓存过期的时候,这个时候通过LastModified属性来判断是否需要更新缓存副本
- lastModified
- if-modified-since 比较两次请求时间内是否有过修改
- ETag 资源唯一标识符,随response返回
- 浏览器获取缓存的过程 根据httpheader去判断是否命中强缓存,命中直接获取,不在发送请求到服务器 强缓存未命中的时候,客户端会发送请求到服务器,去验证是否命中协商缓存,如果命中,服务器返回,但是不返回资源,客户端根据请求的结果,获取缓存,协商缓存也未命中的时候,就需要返回资源给客户端
浏览器跨标签页通讯
通过共享的一些中间介质进行通讯
浏览器跨域
jsonp
function jsonp(url,jsonCallBack,success) {
const script=document.createElement('script')
script.url=url
script.async=true
script.type='text/script'
window[jsonCallBack]=function (data) {
success&&success(data)
}
document.body.appendChild(script)
}
cros
设置 ACESS-ALLOW-ORIGIN:true
xss攻击
注入恶意代码
- cookie设置httponly
- 转义页面上输入和输出内容
csrf攻击
跨域请求伪造,防护
-
get不修改数据
-
不能让第三方网站访问用户的cookie
-
设置白名单
-
请求校验
css知识
盒模型
dom元素采取的布局模型
- 标准盒模型 div的宽高不包含边界和内外填充,只是内容的高度 border-siziing:content-box
- 怪异盒模型 div的宽高是包含边界和内填充 box-sizing:border-box
bfc
块级格式上下文,每个人box都是独立的渲染区,与外界互不影响 其实简单的来说每个box都是独立的,不会受外界影响的块
触发bfc
- overflow属性不为visible
- flex布局
- float的属性
- display为inline-block
- position为absolute,fixed
清除浮动
- 通过增加尾元素来 :after
- clear:both
- 父级设置BFC
- 父级设置高度
常见的手写css布局
垂直居中
div {
text-align :center;'
}
右边宽度固定左边自适应
1.flex布局实现,左边固定宽度,右边flex:1 父容器display:flex
.parent{
display: flex;
}
.left{
flex: 1;
background: black;
}
.right{
width:200px;
background: red;
}
2.float属性实现:float加margin right
.left{
width: 200px;
background: black;
float: right;
height: 100%;
}
.right{
height: 200px;
margin-right:200px;
background: red;
}
水平垂直居中的布局
1.transform结合position
.parent{
position:relative;
}
.view{
width: 200px;
background: black;
float: right;
height: 100%;
position: absolute;
top:50%;
left:50%;
transform:translate(-50%,-50%) ;
}
sass less
类css语言通过webpack编译成真正的css,,常用语法 变量,minxin复用
4.webpack篇
webpack是javascript的模块化打包工具,通过分析模块之间的依赖,最终将所有模块打包成一份或者多份代码包(bundle) 核心概念
- entry 入口文件,webpack会从此文件开始编译分析
- output 出口 打包后创建bundle的路径
- module 模块
- chunk 代码块
- loader 模块加载器
- plugin 插件
webpack的打包流程
- 初始化:读取配置文件,初始化配置,创建compile对象
- 编译:挂载插件监听,从入口文件开始编译
- 编译:根绝不同的文件,加载不同的loader进行编译
- 打包:将打包好的代码块包装成一个chunk,根据依赖和配置,输出固定内容
- 输出:输出到对应的output目录生成文件
webpack常用的loader
- fileLoader
- tsloader
- styleloader
- cssloader
- babelloader
webpack 热更新原理
websocket
webpack打包优化
5.项目相关
对你的项目进行优化
- 减少http请求
- 减少dns查询
- 使用cdn
- 减少操作dom
- 压缩js,css字体图片
vue 性能优化
- 代码层面优化
- 通过object.freeze() 来冻结不需要变化的数据,减少响应系统的负担
- 防抖和节流
- 利用挂载节点会替换优化白屏问题
- 组件库的按需引入
- 项目打包的优化
- 异步按需引入组件
- 代码分割
- js压缩
- 提取第三方库,并使用cdn引入
- 压缩图片
- 项目部署的优化
-
识别gzip压缩是否开启
5. http相关知识补充
http协议
http1.0
http1.1
- 长连接复用
- host指定虚拟站点
- 断点续传,身份认证,状态管理,cache缓存
http2.0
- 多路复用
- 首部压缩
https协议:
- 证书
- ssl加密
- 端口443
常见状态码
- 1xx
- 200
- 404
- 400
- 500
websocket协议
长连接的
6. 前端设计模式
- 单例模式
let sigleton = (function () {
// 隐藏class 的构造函数
function FooService () {}
return {
//创建和获取单例的函数和对象
getInstance: function () {
if (!fooService) {
fooService = new FooService()
}
return fooService
}
}
})()
- 观察者模式
function Subject() {
this.observers=[]
}
Subject.prototype={
subscribe(observer){
this.observers.push(observer)
}
unsubscribe(observerRemove){
this.observers=this.observers.filter(observer=>{
return observer!==observerRemove
})
},
broadcast(){
this.observers.forEach(obsever=>{
obsever.call()
})
}