大钱端之面试(中)
JS基础
JS基础-变量类型和计算
typeof 能判断哪儿些类型
识别出所有值类型 : string char symbol undefind blooean
识别函数 : funtion
识别引用类型 Object(到此为止,不可再细分)
何时使用 === 何时使用 ==
==运算符 会尽量转换成相同类型使之相等
除了等于null之外,一律用三等
值类型和引用类型的区别
值类型 : string char symbol undefind blooean
let a = 100
let b = a
a = 200
console.log(b)
引用类型 : [] {} null 特殊引用类型 |函数类型 function(){}
let a = { age : 20 }
let b = a
b.age = 30
console.log(a.age)
分析
栈 a | 100
栈 a | 100 b | 100
栈 a | 200 b | 100
存储的是内存地址
堆 内存地址1 | { age : 20 }
堆 内存地址1 | { age : 20 }
堆 内存地址1 | { age : 21 }
性能及内存原因,js机制如此
手写深拷贝
注意判断值类型与引用类型 :typeof
注意判断数组还是对象 :instanceof
function deepClone(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
}
变量类型相关的面试题
-
字符串拼接
-
== 判断
-
truely falsely 变量
const n = 100 两部运算后等于true 则为truely变量,反之亦然 !n !!n <-- trurly变量
if(truely)
JS基础-原型与原型链 <基于原型来集成>
知识点:
object可以认为是所有class的父类
class实际上是一个函数
>_ typeof class student{}
>_ function
student.__proto__
student.prototype
student.prototype.__proto__ === people.prototype
suduent 的属性属于自身
方法指向到父类的__proto__
每个class都有显示原型 prototype
实例中的 _ proto _ 对应class的prototype
hasownproperty 验证是否为自身属性
hasownproperty 是object._ proto _的隐式原型中提供的方法
object._ proto _ 永远指向null
至此原型链结束
基于原型的执行规则
- 先在自身的属性和方法寻找
- 找不到自动去上层的 隐式原型 _ proto _中寻找
instanceof 判断引用类型
instanceof 的原理
通过隐式原型在原型链上寻找,找到这个属性方法,返回true 找不到返回false
提示
class是es6语法规范,由ECMA委员会发布
ECMA委员会发布只规定书写规范,不关心如何实现
以上原型都是通过v8引擎的实现发布,也是主流引擎
原型面试题
如何判断一个变量是不是数组
a instanceof array
class的原型本质,怎么理解
原型与原型链的图示
属性和方法的执行规则
手写jquery ,考虑插件与扩展性
class jQuery {
//构造
constructor(selector) {
const result = document.querySelectorAll(selector)
const length = result.length
for (let i = 0; i < length; i++) {
this[i] = result[i]
}
this.length = length
this.selector = selector
}
get(index) {
return this[index]
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const elem = this[i]
fn(elem)
}
}
on(type, fn) {
return this.each(elem => {
elem.addEventListener(type, fn, false)
})
}
// 扩展很多 DOM API
}
// 插件
jQuery.prototype.dialog = function (info) {
alert(info)
}
// “造轮子”
class myJQuery extends jQuery {
constructor(selector) {
super(selector)
}
// 扩展自己的方法
addClass(className) {
}
style(data) {
}
}
// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))
JS基础-作用域及闭包
知识点
作用域和自由变量
作用域:
一个变量的合法使用范围
- 全局作用域
- 函数作用域
- 块级作用域 {}
自由变量 :
一个变量在当前函数体被使用,但没有找到,则一层一层递增查找
所有自由变量的查到,实在函数定义的时候想上寻找,而不是在函数执行的地方向上寻找
- 如果没有找到,则报 xx is not defind
闭包 closure
作用域应用的特殊情况,有两种情况
-
函数作为返回值
function create() { const a = 100 return function () { console.log(a) } } const fn = create() const a = 200 fn() // 100
-
函数作为参数
function print(fn) { const a = 200 fn() } const a = 100 function fn() { console.log(a) } print(fn) // 100
!!! 所有自由变量的查到,实在函数定义的时候想上寻找,而不是在函数执行的地方向上寻找
this
this 有几种赋值情况
- 作为普通函数 < 指向 window >
- 使用 call bind apply < 指向执行体,传入什么绑定什么 >
- 作为对象方法被调用 < 返回当前对象 >
- 在class方法中被调用 < 返回class实例本身 >
- 箭头函数 < 找上级作用域的this来确定 >
this取什么值,是在函数执行的时候定义的,而不是创建的时候 , 适用于以上场景
面试题:
1. this的不同应用场景,如何取值
上面已回答
2. 手写bind函数 [改变指向的方法之一]
**bind()**
方法创建一个新的函数,在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
Function.prototype.bind1 = function (){
把传入的参数变为数组
.call 是把arguments赋值成为当前参数的this 复习 【指向执行体,传入什么绑定什么】
const args = Array.prototype.slice.call(arguments)
剔除args[0]并赋值给变量t <也就是传入的this>
const t = args.shift()
// self 就是调用bind方法的函数本身
const self = this
// 返回一个函数
return function(){
return self.apply(t,args)
}
}
3. 实际开发中闭包的应用场景,举例说明
-
隐藏数据
-
做一个简单的cache工具
function createCache(){ const data = {} return { set:function(key ,value){ data[key,value] }, get:function(key){ return data[key] } } }
4. 创建10个A标签,点击弹出对应的序号
let a
for(let i = 0;i < 10 ; i++){
a = document.createElemeat('a')
a.innerHtml = i+'<br>'
a.addEventListener('click',function(e){
e.prevenDeafualt()
e.alert(i)
})
document.body.appendchild(a)
}
JS基础-异步
JS三座大山 原型和原型链 作用域和闭包 以及异步和单线程
Promise 解决了 callback hell的问题
JS是一门单线程语言,只能同时干一件事
浏览器和nodejs已经支持js同时启动进程,例如web worker
js和DOM渲染同时使用同一个线程,因为JS可以修改DOM结构 <DOM渲染过程中JS必须停止,JS执行过程中也必须停止 >
遇到等待,(定时任务,网络请求),页面不能卡住
需要异步
异步是基于callback函数形式来调用的
异步 :
- 基于JS是单线程语言
- 异步不会阻塞代码执行
- 同步会阻塞代码执行
异步的应用场景:
- 网络请求 : 如 ajax图片加载
- 定时任务 : setTimeout setInterval
- 解决回调地狱 promise把回调变成了管道类的形式,而不是嵌套,越陷越深.
面试题
同步和异步的区别是什么
阻塞线程的区别
- 基于JS是单线程语言
- 异步不会阻塞代码执行
- 同步会阻塞代码执行
手写promise加载一张图片
const url1 = 'xxx.jpg'
const getImage (src){
return new Promise((resolve,reject)=>{
const img = document.careteElment('img')
img.onload = () => {
resolve(img)
}
img.onerror = () =>{
const err = new Error(`err info :${src}`)
rject(err)
}
img.src = src
})
}
前端异步的使用场景有哪儿些
- 网络请求 : 如 ajax图片加载
- 定时任务 : setTimeout setInterval
JS-异步进阶
JS异步的原理及进阶
-
event-loop
请描述 event-loop 的机制,可画图 别名:《事件循环/事件轮询》
-
Promise进阶
Promise有哪儿三种状态,如何变化
Promise then与catch的连接
Promise 和 setTimeout 的顺序
-
aysnc await
async await 语法
-
微任务 / 宏任务
什么是微任务,宏任务,两者有啥子区别
什么是event-loop (事件循环/事件轮询)
- JS 是单线程
- 异步通过回调来实现
event loop就是异步回调的实现原理
JS是如何执行
- 从前到后,一行一行执行 , 如果中间有一行报错,则停止执行
- 先把同步代码执行完,再执行异步
console.log('hi')
setTimeout(function cb1(){
console.log('cb1')
},5000)
console.log('bye')
以上JS代码执行过程
call stack (调用栈)--> web apis (处理api)--> callback queue (回调函数队列)--> Event loop (事件循环)
hi 的执行顺序:
1. call stack 放入 console.log('hi')
2. browser console output: hi
3. call stack (empty)
setTimeout 的执行
1. call stack 放入 setTimeout cb1
2. web apis 放入定时器 timer cb1 > 5秒钟的定时器
3. 5秒钟的定时器结束后 > cb1 放入 callback queue
bye 的执行顺序:
1. call stack 放入 console.log('bye')
2. browser console output: bye
3. call stack (empty)
所有同步代码执行完成后(调用栈为空),启动event loop机制
1.启动后在 callback queue轮询查找
2.在callback queue里找到cb1 推到 ,call stack里执行函数体cb1
3.执行cb1,打印 cb1 ,完成后从调用栈 清空
4.继续轮询查找 (~永动机一样~)
5.JS语句执行完毕
DOM事件和event-loop的关系
DOM事件也是基于回调来实现的
只要是基于回调,就是基于eventloop来实现的
btn的click触发
当执行到绑定click JS语句是,把click方法推到webapi存储,当用户点击时触发,webapi推送至callback queue ,eventloop轮询
Promise有哪儿三种状态
三种状态:
- pending 过程中
- resolved 成功 触发 then
- rejected 失败 触发 catch
状态变化是不可逆的
状态的表现和变化
then 和 catch 改变状态
- then 正常返回一个resolved 的Promise, 如果有报错, 则返回一个 rejected 的Promise
- catch正常返回一个resolved 的Promise, 如果有报错, 则返回一个 rejected 的Promise
async await 语法
- 异步回调 callback hell
- Promise then catch 链式调用,但也是基于回调函数
async await 是同步语法去编写异步代码,彻底的消灭了回调函数
async 相当于封装了Promise 又返回一个Promise
await 相当于 Promise的then
async await和Promise的关系
- Async Await是消灭异步回调的终极武器
- 但是和Promise并不互斥
- 两者是相辅相成的
执行Async函数,返回的是一个Promise , 无论返回什么,都会封装成一个Promise对象
await相当于Promise的then()
try...catch 替代了Promise的catch
异步的本质
- Async Await是消灭异步回调的终极武器
- JS还是单线程,该异步还要异步,还是基于event-loop来实现的
- Async Await 只是个语法糖,但是这个糖好吃
- Async Await 只是语法层面像同步的写法,但本质脱离不了异步
async function async1 () {
console.log('async1 start')
await async2() // await 后面所有的语法 都可以当成callback执行
console.log('async1 end') *// 关键在这一步,它相当于放在 callback 中,最后执行*
}
async function async2 () {
console.log('async2')
}
console.log('script start')
async1()
console.log('script end')
你猜猜打印的顺序~~~~ 异步的本质!
for ...of 的应用场景
-
for ... in forEach 会同时执行遍历,因为属于同步语法
-
for ... of 常用在异步的遍历
第一次遍历有结果后才会执行第二次遍历
宏任务 macro Task,微任务micro Task(event-loop事件 异步)
什么是宏任务 macro Task,微任务micro Task
-
宏任务 > setTimeout setInterval ajax DOM事件
-
微任务 > Promise async await
微任务的执行时机比宏任务要早
event-loop 和 dom渲染
为什么微任务执行时机比宏任务要早
-
第一次同步代码全部执行完,执行栈(call stack)空闲, 会尝试DOM渲染 , 然后才会执行 event-loop机制
- 每次轮询结束一次之后,DOM结构如果有变化的话, 会再次尝试DOM渲染
3. 再次触发下一次的 event - loop
- 每次轮询结束一次之后,DOM结构如果有变化的话, 会再次尝试DOM渲染
微任务 宏任务的区别
宏任务 : DOM渲染后触发 setTimeout
微任务 :DOM渲染前触发 Promise
为什么微任务执行时机要比宏任务早
Promise 是 ES6规范,不是W3C规范,它不会进入webapi 及 callback queue
而是等待时机推入 mircro queue [微任务队列]
所以 event-loop的执行步骤不一样
- call stack 清空
- 执行微任务
- 尝试DOM渲染
- event-loop
- 执行宏任务
微任务是ES6语法规定的
宏任务是由浏览器规定的
总结:
- event loop
- Promise 进阶
- async await
- 微任务 宏任务
JS-web-API
- JS 基础知识 规定语法 ECMA 262标准
- 变量类型计算
- 原型和原型链
- 作用域和闭包
- JS Web API 网页操作的API W3C标准
- DOM 网页节点
- BOM 浏览器的事情(导航,url ,跳转)
- 事件绑定
- ajax
- 存储
- 前者是后者的基础,两者结合才能挣钱
DOM的本质是什么
Document Object Model
vue 和 react 框架应用广泛,已经封装了Dom操作
DOM操作是必备知识,不可不会,框架只是个框架
vue 与 react 的底层也是在操作DOM节点
DOM 的本质 : 从html语言解析出来的一棵树~
所以叫 DOM 树
DOM 节点操作
获取DOM节点
getElementById
getElementByTagName
getElementByClassName
querySelectorAll
attribute
直接修改标签的属性,会改变html结构
getAttribute('')
setAttribute('style','font-size:50px;')
property
纯操DOM 修改js变量,不会体现到html结构中
let p = document.getElementById('p')
p.style.width = '100px'
p.style.color = '100px'
p.calssName = ''
p.nodeName
p.nodeType
- 两者都能引起DOM的重新渲染
DOM 结构操作
新增节点
let div1 = document.getElementById('div1')
let p1 = document.createElement('p')
p1.innerHtml = '<p>this is p1</p>'
div1.appendChild(p1)
移动节点
对现有div 插入已存在节点,会移动此节点
获取子元素列表
let div1 = document.getElementById('div1')
div1.childNodes
过滤获取的子元素
let div1 = document.getElementById('div1')
const divChildP = Array.prototype.slice.call(div1.childNodes).filter((child)=>{
if(child.NodeType === 1){
return true
}
return false
})
console.log(divChildP)
删除子元素
div1.removeChild( divChildP[0] )
获取父元素
p1.parentNode
DOM 性能
如何优化DOM性能
-
DOM的操作非常昂贵,要避免频繁的操作DOM
-
对DOM查询做缓存
-
缓存length
let dd = document.getElementByTagName('div') let length = dd.length for(let i = 0 ; i< length ; i++){ 对length进行缓存 }
-
-
将多次操作改为一次操作
创建一个文档片段 let frg = document.createDocumentFragment() 将需要插入的内容 appendchild 到fragment中 fragment.appendchild(items) div1.appendchild(fragment)
面试题:
DOM是什么数据结构?
DOM操作的常用API?
attribute 和 property 的区别?
一次性插入多个DOM,考虑性能?
JS-WEB-API-BOM
Browser Object Model
- 如何识别浏览器的类型
- 分解拆解url的各个部分
知识点
- navigator.userAgent ( ua ) 拿到浏览器的信息
- screen 屏幕信息
- location 地址信息 url的信息
- location.herf 整个url
- location.protocol 请求协议 http/https
- location.host 域名
- location.search 常用信息
- location.hash #号后面的内容
- location.pathname 浏览器路径(/后的内容)
- history 前进后退信息
- history.back 后退
- history.forward 前进
JS-WEB-API-事件
知识点:
事件绑定
const div = document.getElementById('xx')
div.addEventListener('click',(event)=>{
console.log('click')
})
事件冒泡
const div = document.getElementById('xx')
div.addEventListener('click',(event)=>{
event.stopPropagation() 阻止冒泡
console.log('冒泡')
})
const body = document.body
body.addEventListener('click',(event)=>{
console.log('body冒泡')
})
event.prevenDefault() //阻止默认行为
event.stopPropagation() 阻止冒泡
事件代理
什么是事件代理
把事件绑定到不好绑定或者没法绑定元素的父元素上
只在父元素上绑定一个事件,处理子元素的事件
面试题
-
编写一个通用的事件监听函数
function bindEvent (elem , type , fn ){ elem.addEventListener(type, fn) } let dd = document.getElementById('xx') bindEvent(dd,'click',event => { event.target //获取当前被绑定对象 event.prevenDefault() //阻止默认行为 event.target.matches('a') //判断是否包含a标签 alert('clicked') })
进化版事件监听,增加过滤条件
// 通用的事件绑定函数 // function bindEvent(elem, type, fn) { // elem.addEventListener(type, fn) // } 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) } }) }
-
描述事件冒泡的流程
事件会随着触发元素向上冒泡 最高body, 所以才可以实现 事件代理
-
无限下拉图片列表(瀑布式),如何监听每个图片的点击 ?
1. 事件代理 2. target 获得触发元素 3. matches 来判断是否为触发元素
JS-WEB-API-Ajax
知识点:XMLHttpRequest
GET
const xhr = new XMLHttpRequest()
// 是否异步处理 false 否
xhr.open('GET',"/api",true)
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.stats === 200){
JSON.parse(xhr.responseText) //转换为json格式
alert(xhr.responseText)
}
}
}
xhr.send(null)
POST
const user = {
username : 'xxx'
password : '123'
}
const xhr = new XMLHttpRequest()
// false 为异步处理
xhr.open('POST',"/api",false)
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status === 200){
JSON.parse(xhr.responseText) //转换为json格式
alert(xhr.responseText)
}
}
}
xhr.send(JSON.stringfly(user))
知识点:状态码
- readyState
- 0 init
- 1 readysend
- 2 sending
- 3 decode
- 4 ok
- status
- 2xx sccuess
- 3xx redirect
- 301 always redirect
- 302 temp redirect
- 304 use cache to reload
- 4xx error
- 404 errurl or has no auth to visit
- 405
- 5xx Server error
知识点:跨域:同源策略,跨域解决方案
什么是同源策略
!浏览器要求当前网页和Server端必须同源
同源 : 域名,协议,端口必须一致
图片 CSS JS 无视跨域
-
可以实现统计打点
- 可以使用CDN,CDN一般为外域