基础js面试总结
本人前端新手,小白一枚,最近在看慕课的视频,花了大概两三天时间吧,总结了下一些基础js的面试的内容,第一次写文章有不足的地方,还望大家多多批评指正
一.变量类型和计算
1.变量类型
值类型:直接存储在内存,包含:number,undefined,String,boolean,symbol(es6新类型).
引用类型:堆栈模型,会在堆里申请一个内存,变量从内存中取.包含:
- Object-对象
- Array-数组
- null-指针指向空地址
- 函数--特殊引用类型,不用于数据存储,没有拷贝复制的说法
补充:堆栈模型
//引用类型
let a={age:20}
let b=a
b.age=21
console.log(a.age) //21

2.typeof运算符
识别所有值类型,识别所有函数,可以判断是否为引用类型(不可再进行细分)
3.深拷贝
手写一个深拷贝
/**
* 深拷贝
*/
const obj1 = {
age: 20,
name: 'xxx',
address: {
city: 'beijing'
},
arr: ['a', 'b', 'c']
}
const obj2 = deepClone(obj1)
obj2.address.city = 'shanghai'
obj2.arr[0] = 'a1'
console.log(obj1.address.city)
console.log(obj1.arr[0])
/**
* 深拷贝
* @param {Object} obj 要拷贝的对象
*/
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
// obj 是 null ,或者不是对象和数组,直接返回
return obj
}
// 初始化返回结果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归调用!!!
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}
4.计算
1.类型转换
字符串拼接+号前后会拼接成字符串
2.==与===
==会进行强制的类型转换,把两边的类型转换一致进行比较,===不会,两边的类型必须相等才成立,在开发过程中,除了==null,其他的一律使用===
3.if语句和逻辑运算
在做if逻辑运算时,判断的是两步非运算后的结果:truly变量和falsely变量
特殊的falsely变量:
0===false
NaN===false
" "===false
null===false
undefined===false
false===false
二.原型和原型链
1.class和继承
1.class-->面向对象
constructor构建:属性,方法
// 类
class Student {
constructor(name, number) {
this.name = name
this.number = number
// this.gender = 'male'
}
sayHi() {
console.log(
`姓名 ${this.name} ,学号 ${this.number}`
)
}
}
// 通过类 new 对象/实例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name)
console.log(xialuo.number)
xialuo.sayHi()
const madongmei = new Student('马冬梅', 101)
console.log(madongmei.name)
console.log(madongmei.number)
madongmei.sayHi()
extends继承类
super->传递给父类去执行
// 父类
class People {
constructor(name) {
this.name = name
}
eat() {
console.log(`${this.name} eat something`)
}
}
// 子类
class Student extends People {
constructor(name, number) {
super(name)
this.number = number
}
sayHi() {
console.log(`姓名 ${this.name} 学号 ${this.number}`)
}
}
// 子类
class Teacher extends People {
constructor(name, major) {
super(name)
this.major = major
}
teach() {
console.log(`${this.name} 教授 ${this.major}`)
}
}
// 实例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name)
console.log(xialuo.number)
xialuo.sayHi()
xialuo.eat()
// 实例
const wanglaoshi = new Teacher('王老师', '语文')
console.log(wanglaoshi.name)
console.log(wanglaoshi.major)
wanglaoshi.teach()
wanglaoshi.eat()
2.类型判断instanceof
判断变量属于哪个class或者构造函数
//借用上文的class
xialuo instanceof Student //true
xialuo instanceof People //true
xialuo instanceof Object //true
[] instanceof Array //true
[] instanceof Object //true
{} instanceof Object //true
3.原型和原型链
1.原型关系
- 每个class都有显示原型prototype
- 每个实例都有隐式原型_proto_
- 实例的_proto指向对应class的prototype
2.基于原型的执行规则
- 获取属性xialuo.name或执行xialuo.sayhi()时
- 先在自身属性和方法寻找
- 找不到自动去隐式原型_proto_中查找
3.hasOwnProperty()
验证是不是自己的属性
4.原型链

5.重要提示
- class是ES6语法规范,由ECMA委员会发布
- ECMA只规定语法规则,即我们代码的书写规范,不规定如何实现
- 以上实现方式都是V8引擎的实现方式,也是主流
三.作用域和闭包
1.作用域和自由变量
作用域:变量的合法使用范围
- 全局作用域
- 函数作用域
- 块级作用域:let和const
自由变量:变量在当前作用域没有定义但被使用了,向上级作用域,一层一层一次寻找,找到位置,如果全局作用域都没找到,会报not defined;
2.闭包
作用域应用打的特殊情况,有两种表现:
- 函数作为参数被传递
- 函数作为返回值被返回
自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方
//函数作为返回值
function create() {
let a = 100
return function () {
console.log(a);
}
}
let fn = create()
let a = 200
fn()//100
//函数作为参数
function print(fn){
let a= 200
fn()
}
let a=100
function fn(){
console.log(a);
}
print(fn) //100
// 所有的自由变量的查找,是在函数定义的地方,向上级作用域查找
// 不是在执行的地方
3.this
1.应用场景
作为普通函数被调用-->window
使用call apply bind-->传入什么绑定shenme
作为对象方法被调用-->对象本身
在class方法中调用-->实例本身
箭头函数-->找上级作用域this的值
2.取值
this在函数执行的时候确认的,不是在函数定义的时候确认的
3.手写bind函数
// 模拟 bind
Function.prototype.bind1 = function () {
// 将参数拆解为数组
const args = Array.prototype.slice.call(arguments)
// 获取 this(数组第一项)
const t = args.shift()
// fn1.bind(...) 中的 fn1
const self = this
// 返回一个函数
return function () {
return self.apply(t, args)
}
}
function fn1(a, b, c) {
console.log('this', this)
console.log(a, b, c)
return 'this is fn1'
}
const fn2 = fn1.bind1({
x: 100
}, 10, 20, 30)
const res = fn2()
console.log(res)
4.实际开发中闭包的应用
- 隐藏数据
- 做一个简单的cache工具
// 闭包隐藏数据,只提供 API
function createCache() {
const data = {} // 闭包中的数据,被隐藏,不被外界访问
return {
set: function (key, val) {
data[key] = val
},
get: function (key) {
return data[key]
}
}
}
const c = createCache()
c.set('a', 100)
console.log( c.get('a') )
四.异步和单线程
1.单线程和异步
1.js语言单线程:
js是单线程语言,同时只能做一件事儿
浏览器和nodejs已支持js启动进程,如Web Worker
JS和DOM渲染共用一个线程,因为JS可修改DOM结构
遇到等待(网络请求,定时任务)不能卡住
回调基于callback形式
2.为什么使用异步
基于js是单线程语言,同步会阻塞代码执行,异步不会阻塞代码执行
3.应用场景
1.网络请求,如ajax图片加载
2.定时任务,如setTimeout
4.定时器
setTimeou和setInterval
2.promise
1.回调地狱
2.promise
resolve和reject两个参数
手写promise加载图片
function loadImg(src) {
const p = new Promise(
(resolve, reject) => {
const img = document.createElement('img')
img.onload = () => {
resolve(img)
}
img.onerror = () => {
const err = new Error(`图片加载失败 ${src}`)
reject(err)
}
img.src = src
}
)
return p
}
const url1 = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
const url2 = 'https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg'
loadImg(url1).then(img1 => {
console.log(img1.width)
return img1 // 普通对象
}).then(img1 => {
console.log(img1.height)
return loadImg(url2) // promise 实例
}).then(img2 => {
console.log(img2.width)
return img2
}).then(img2 => {
console.log(img2.height)
}).catch(ex => console.error(ex))
五.JS Web API
1.DOM
1.前言
vue和react框架应用广泛,封装了DOM操作
但DOM操作一直都会是前端工程师的基础,必备知识
只会vue而不懂DOM操作的前端程序员,不会长久
2.DOM的本质
DOM树:浏览器已经加载好的HTML,一棵一层一层向下的树
3.DOM节点操作
- 获取DOM节点
const div1 = document.getElementById('div1')//通过id获取
const divList = document.getElementsByTagName('div') // 集合,通过标签名
const containerList = document.getElementsByClassName('container') // 集合,通过类名
const pList1 = document.querySelectorAll('p')
const pList2 = document.querySelectorAll('p')
- attribute
setAttribute和getAttribute两个方法,通过这两个方法修改dom结构
// attribute
p1.setAttribute('data-name', 'imooc')
console.log( p1.getAttribute('data-name') )
p1.setAttribute('style', 'font-size: 50px;')
console.log( p1.getAttribute('style') )
- Property
以js对象属性的形式来操作,不会体现到html结构中
// property 形式
p1.style.width = '100px'
console.log( p1.style.width )
p1.className = 'red'
console.log( p1.className )
console.log(p1.nodeName)
console.log(p1.nodeType) // 1
4.DOM结构操作
// 新建节点
const newP = document.createElement('p')
newP.innerHTML = 'this is newP'
// 插入节点
div1.appendChild(newP)
// 移动节点
const p1 = document.getElementById('p1')
div2.appendChild(p1)
// 获取父元素
console.log( p1.parentNode )
// 获取子元素列表
const div1ChildNodes = div1.childNodes
console.log( div1.childNodes )
const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes).filter(child => {
if (child.nodeType === 1) {
return true
}
return false
})
console.log('div1ChildNodesP', div1ChildNodesP)
// 删除节点
div1.removeChild( div1ChildNodesP[0] )
5.DOM性能
DOM操作非常珍贵,避免频繁的DOM操作
对DOM查询做缓存:
//不缓存DOM查询结果
for (let i = 0; i < document.getElementsByTagName('p').length; i++) {
//每次循环,都会计算length,频繁进行DOM查询
}
//缓存DOM查询结果
const pList = document.getElementsByTagName('p')
const length = pList.length
for (let i = 0; i < length; i++) {
//缓存length,只进行一次DOM查询
}
将频繁的操作改为一次性操作
const list = document.getElementById('list')
// 创建一个文档片段,此时还没有插入到 DOM 结构中
const frag = document.createDocumentFragment()
for (let i = 0; i < 20; i++) {
const li = document.createElement('li')
li.innerHTML = `List item ${i}`
// 先插入文档片段中
frag.appendChild(li)
}
// 都完成之后,再统一插入到 DOM 结构中
list.appendChild(frag)
console.log(list)
2.BOM
- nabigator 浏览器信息
- screen 屏幕信息
- location 地址信息
- history 历史记录
//nabigator
const ua = navigator.userAgent
const isChrome = ua.indexOf('Chrome')
console.log(isChrome)
//screen
console.log(screen.width)
console.log(screen.height)
//location
console.log(location.href);
console.log(location.protocol);//"http:" "https:"
console.log(location.pathname);
console.log(location.search);
console.log(location.hash);
//history
history.back()
history.forward()
3.事件绑定
1.事件绑定
通用事件绑定函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>事件 演示</title>
<style>
div {
border: 1px solid #ccc;
margin: 10px 0;
padding: 0 10px;
}
</style>
</head>
<body>
<button id="btn1">一个按钮</button>
<!-- <div id="div1">
<p id="p1">激活</p>
<p id="p2">取消</p>
<p id="p3">取消</p>
<p id="p4">取消</p>
</div>
<div id="div2">
<p id="p5">取消</p>
<p id="p6">取消</p>
</div> -->
<div id="div3">
<a href="#">a1</a><br>
<a href="#">a2</a><br>
<a href="#">a3</a><br>
<a href="#">a4</a><br>
<button>加载更多...</button>
</div>
<script src="./event.js"></script>
</body>
</html>
// 通用的事件绑定函数
function bindEvent(elem, type, selector, fn) {
if (fn == null) {
fn = selector
selector = null
}
elem.addEventListener(type, event => {
const target = event.target
if (selector) {
// 代理绑定
if (target.matches(selector)) {
fn.call(target, event)
}
} else {
// 普通绑定
fn.call(target, event)
}
})
}
// 普通绑定
const btn1 = document.getElementById('btn1')
bindEvent(btn1, 'click', function (event) {
// console.log(event.target) // 获取触发的元素
event.preventDefault() // 阻止默认行为
alert(this.innerHTML)
})
// 代理绑定
const div3 = document.getElementById('div3')
bindEvent(div3, 'click', 'a', function (event) {
event.preventDefault()
alert(this.innerHTML)
})
2.事件冒泡
基于DOM树形结构
事件会顺着触发元素往上冒泡
3.事件代理
基于事件冒泡
优点:代码简洁,减少浏览器内存使用,不要滥用
4.ajax
//get请求
const xhr = new XMLHttpRequest()
xhr.open('GET', '/data/test.json', true)
xhr.onreadystatechange = function () {
//这里的函数异步执行,可参考之前JS基础中的异步模块
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// console.log(
// JSON.parse(xhr.responseText)
// )
alert(xhr.responseText)
} else if (xhr.status === 404) {
console.log('404 not found')
}
}
}
xhr.send(null)
xhr.readyState
0-(未初始化)还没调用send()方法
1-(载入)已调用send(),正在发送请求
2-(载入完成)send()方法执行完成,已经接收到全部响应内容
3-(交互)正在解析响应内容
4-(完成)响应内容解析完成,可以在客户端调用
xhr.status
2xx-表示成功处理请求,如200
3xx-需要重定向,浏览器直接跳转,如301 302 304
4xx-客户端请求错误,如404,403
5xx-服务器端错误
同源策略
ajax请求时,浏览器请求当前网页和server必须同源
同源:协议,域名,端口,三者必须一致
跨域
所有的跨域都必须经过server端允许和配合
未经server端允许就实现跨域,说明浏览器有漏洞,危险信号
解决跨域
1.JSONP
script可绕过跨域限制
服务器可以任意动态拼接数据返回
可以利用script就可以获得跨域的数据,只要服务端愿意返回
2.CROS--服务器设置http header

5.存储
1.cookie
本身用于浏览器和server通讯
被"借用"到本地存储来
可用document.cookie="..."来修改
cookie的缺点
- 存储大小,最大4KB
- http请求时需要发送到服务器,增加请求数据量
- 只能用document.cookie="..."来修改,太过简陋
2.localStorage和sessionStorage
共同点
- HTML5专门为存储设计的,最大可存5M;
- API简单易用
- 不会随着http请求被发送出去
区别:
- localStorage数据会永久存储,除非代码或手动删除
- sessionStorage数据只存在于当前会话,浏览器关闭则清空
- 一般用localStorage会更多一些
六.开发环境问题
1.关于开发环境
- 面试官想通过开发环境了解候选人的实际工作情况
- 开发环境的工具,能体现工作效率的产出
- 会以聊天形式为主,不会问具体的问题
2.git
- 最常用的代码版本管理工具
- 大型项目需要多人协作开发,必须熟用git
- 常用命令:
git add . (添加)
git checkout XXX (恢复文件状态)
git commit -m 'XXX' (提交一行记录)
git push origin master (提交)
git pull origin master (拉取)
git branch (查看分支)
git checkout -b xxx (切换分支)
git merge xxx (合并)
3.调试工具
一般是浏览器的内部调试工具
4.抓包
- 移动端页,查看网络请求,需要用工具抓包
- windows一般用fiddler
- Mac OS一般用charles
抓包过程
- 手机和电脑连同一个局域网
- 将手机代理到电脑上
- 手机浏览网页,即可抓包
5.性能优化
1.性能优化原则
- 多实用内存,缓存,活其他方法
- 减少CPU计算量,减少网络加载耗时
- 空间换时间
2.从何入手
- 让加载更快
- 让渲染更快
3.让加载更快
- 减少资源体积:压缩代码
- 减少访问次数:合并代码,SSR服务器端渲染,缓存
- 使用更快网络:CDN
4.让渲染更快
- CSS放head,JS放在body最下面
- 尽早开始执行JS,用DOMContentLoaded触发
- 懒加载
- 对DOM查询进行缓存
- 频繁DOM操作,合并到一起插入DOM结构
- 节流throttle 防抖debounce