变量类型和计算
-
typeof
能判断哪些类型
1. 能判断所有的值类型
字符串(string)、数值(number)、布尔值(boolean)、undefined、null、symbol
2. 能判断函数
3. 能判断是不是引用类型(无法细分),想细分怎么办?使用instanceof
- 何时使用
===
何时使用==
1. 除了==null外 其余的都要用===
-
值类型
和引用类型
的区别
1. 堆栈的角度去判断
- 手写
深拷贝
function deepClone(obj) {
if(obj == null || typeof obj !== 'object'){
return obj
}
let result = null;
if(obj instanceof Array){
result = []
}else{
result = {}
}
for(let key in obj){
result[key] = deepClone(obj[key])
}
return result
}
原型和原型链
- instanceof 的本质
对象a由函数A创建,当使用```a instanceof A```的时候,会出现:a顺着__proto__往上找,A顺着prototype往上找,如果两者在同一个地方相遇,即返回true。
- 如何判断一个变量是不是
数组
1. arr instaceof Array
2. Array.isArray
3. Object.prototype.toString.call(arr)
- 手写一个
简易的jQuery
,考虑插件
,考虑拓展性
class jQuery {
constructor(selector){
let dom = document.querySelectorAll(selector)
const length = dom.length
for(let i=0;i<length;i++){
this[i] = dom[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){
this.each(elem=>{
elem.addEventListener(type,fn,false)
})
}
// 拓展api
}
/**
let divs = new jQuery('div')
divs.each(div=>{
console.log(div);
})
divs.on('click',(e)=>{
console.log(e);
})
console.log(divs.get(1));
*/
// 插件机制
jQuery.prototype.dislog = function () {
// 添加插件
}
// 复写(造轮子)
class myJQuery extends jQuery{
constructor(selector) {
super(selector);
}
// 拓展自己的方法
// ...
}
-
extends
的本质,怎么理解
例如:B extends A
实现:B._ _ _proto_ _ _ = A B.prototype._ _ _proto_ _ _ = A.prototype
作用域和闭包
-
this
的不同应用场景,如何取值
1. 构造函数(this -> 当前对象)
2. 函数作为对象的一个属性
let obj = {
aa:111,
b:function () {
console.log(this);
}
}
obj.b()
- 手写
bind/call/apply
函数
function a(args) {
console.log(this);
console.log('aaa',args);
}
let b = {
aaa:111
}
Function.prototype.myBind = function () {
let args = [...arguments]
let context = args.shift()
let _this = this
return function () {
_this.apply(context,arguments)
}
}
// a.myBind(b,[1,2,3])()
Function.prototype.myCall = function () {
let args = [...arguments]
let context = args.shift()
context.fn = this
const result = context.fn(...args)
delete context.fn
return result
}
a.myCall(b,1,2,3)
// a.apply(b,[1,2,3])
Function.prototype.myApply = function () {
let args = [...arguments]
let context = args.shift()
context.fn = this
let result = context.fn(args)
delete context.fn
return result
}
a.myApply(b,[1,2,3])
- 实际开发中
闭包
的应用场景,举例说明
1. 封装变量,将其封装到函数内部,不让拿到原值进行修改(绑到函数的原型上面,感觉更好)
2. 模拟块级作用域(立即执行函数)
3. 缓存数据
let numCache = function () {
var cache = {}
return function () {
var arg = JSON.stringify(arguments)
if(cache[arg]){
// 从cache中取出
console.log('从cache中取出');
}else{
// 计算
console.log('计算');
let sum = 0
for(let i=0;i<arguments.length;i++){
sum+=arguments[i]
}
cache[arg] = sum
}
}
}
let sum = numCache()
sum(1,2,3) // 计算
sum(1,2,3) // 从cache中取出
- 创建10个
<a>
标签,点击的时候弹出来对应
的序号(let,闭包)
let dom = document.getElementById('myDiv')
let df = new DocumentFragment()
for (var i=0;i<10;i++){
let node = document.createElement('a')
node.innerHTML = 'node '+i
let fn = function(){
var index = i
return function () {
console.log(index);
}
}
/**
* 如果不使用闭包,在onclick的时候,此时执行的函数中的i不存在当前函数执行上下文中,就会向父级作用域(global)查找,找到的i=10
* 使用闭包,在onclick的时候,此时执行函数中的i不存在当前执行函数上下文中,就会向父级作用域(fn)查找,找到了,此时父级作用域中
* 的i是在fn函数声明的时候就创建了,因为作用域是词法环境导致,即函数声明时得到作用域,所以fn中的i就是循环时候的每次都不一样的i,
* 所以找到父级作用域(fn)时,得到的i就是想要的结果。
* */
node.onclick = fn()
/**
* 简单写法
* node.onclick = (function(i){
return function () {
console.log(i);
}
})(i)
*/
df.append(node)
}
dom.append(df)
异步和单线程
-
同步
和异步
的区别是什么
https://www.cnblogs.com/lchsirblog/p/12048695.html -
手写
promise
加载一张图片
function loadImg() {
return new Promise((resolve, reject) => {
let img = document.createElement('img')
img.src = url
img.onload = function () {
resolve(img)
}
img.onerror = function (err) {
reject(err)
}
})
}
- 前端使用
异步
的场景有哪些
1. 网络请求
2. 定时任务
DOM
-
DOM
是哪种数据结构
- 树
-
DOM
本质是什么
- 一棵html树
-
DOM
操作常用的API
- 节点操作
- 查询节点
getElementById / getElementsByTagName / getElementsByClassName / querySelectorAll
- 插入节点
appendChild...
- 移动节点
appendChild() 将现有节点插入到其他内部,会出现移动的效果
-
attribute
和property
的区别
是什么
property : p1.style.width...... // 修改js变量属性,不会体现到html中 (建议)
attribute : p1.setAttribute('data-name',111) // 修改html属性,直接改变html结构
-
一次性
插入多个DOM节点
,考虑性能
- DocumentFragement
- length的时候,赋给一个变量
BOM
- 如何识别浏览器的
类型
- 获取浏览器的信息
- navigator / userAgent
- 获取屏幕的信息
- screen / width height
- 获取地址栏的信息
- location href/protocol/pathname/search/hash
- 前进后退的信息
- history /forward() back()
-
拆解URL
的各个部分
事件
- 编写一个
通用
的事件监听函数
function bindEvent(element, type, selector, fn) {
// selector是事件代理时候用
if(fn == null){
fn = selector
selector = null
}
element.addEventListener(type,function(event){
const target = event.target
if(selector){
// 代理绑定
if(target.matches(selector)){
// dom是否符合selector选择器
fn.call(target,event)
}
}else{
//普通绑定
fn.call(target,event)
}
})
}
- 描述
事件冒泡
的流程
以点击事件为例,用户点击div的时候,会触发div的点击事件监听函数,接着会触发div的父级dom的点击事件监听函数,
一直往上冒,冒到document为止
- 无限下拉的图片列表,如何
监听每个图片的点击
- 使用事件代理,使用父级监听点击函数,然后判断即可。不要每个元素都添加。
- 事件代理
子元素比较多,不需要挨个进行事件绑定,将其事件绑定到父元素上,然后做一些判断即可。
- 优点
代码简洁 、 减少浏览器内存占用 、 不要滥用
ajax
- 手写一个
简易的ajax
let xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
// 0: 请求未初始化
// 1: 服务器连接已建立
// 2: 请求已接收
// 3: 请求处理中
// 4: 请求已完成,且响应已就绪
if(xhr.readyState === 4){
if(xhr.status === 200){
console.log(JSON.parse(xhr.responseText));
}
}
}
xhr.open('get','./data.json')
xhr.send()
-
跨域
常用的实现方式
- jsonp 和 CROS(server做)
- 哪些标签不跨域
-<img> <link> <script>
- fetch是什么
一种异步请求的工具,不同于XMLHttpRequest,是一种新的api,底部实现Promise,
有一个缺点就是返回的promise不会被标记为reject,即使HTTP相应的状态码是404或者500.
存储
- 描述
cookie,localStorage,sessionStorage
的区别(cookie的本质
)
cookie的本质:
- 本身是用于浏览器和server通讯,不是用于本地存储,只是被借用来本地存储。前后端都可以修改cookie。
webpack相关
- 为什么使用webpack
ES6的模块化,浏览器是暂时不支持的。
ES6语法,浏览器并不完全支持
压缩代码,整合代码,以让网页加载更快(缓存机制)
- 安装webpack
```npm install webpack webpack-cli -D --代理```
- babel的用处
将ES6的语法转移为ES5的语法
- 模块化的规范是什么
// 多导出
export function f() {}
export const a = 111
import {fn,a} from './a'
// 单导出
function f1() {}
const aa = 222
export {
f1,aa
}
import {fn,aa} from "./a";
// 导出多个,导入时不解构
let aaa222 = {}
export default aaa222
import aaa222 from './'
页面加载过程
从输入url到渲染出页面的整个过程
# 加载过程
1. DNS(Doman name server)解析:域名 -> IP
2. 浏览器根据IP地址向服务器发起HTTP请求
3. 服务器接处理HTTP请求,并返回给浏览器
# 渲染过程
1. 根据HTML代码生成DOM Tree
2. 根据CSS代码生成CSSOM (CSS 对象模型)
3. 将DOM Tree和CSSOM 整合生成 Render Tree
4. 根据Render Tree渲染页面
5. 遇到script的话 就暂停渲染 有限执行js代码 因为js线程和渲染线程是一个线程
6. 直至把Render Tree渲染完成
- 为何把css放在head中
扫描到head后,发现没有css,此时就去渲染页面,到后面发现css后,可能会修改之前渲染的,
所以会做重复工作,甚至造成页面跳动等情况。
-
window.onload
和DOMContentLoaded
的区别
window.addEventListener('load',function () {
// 网页上所有资源都加载完才会触发,包括耗时的图片等...
})
window.addEventListener('DOMContentLoaded',function () {
// DOM渲染完毕即可执行,此时的图片、视频还可能都没有加载完
})
性能优化
- 前端
常见的性能优化
的方案
# 性能优化
原则: 空间换时间
1. 多使用内存,缓存或者其他方法
2. 减少CPU的计算量,减少网络加载耗时
从何入手:
让加载更快
减少资源体积 - 压缩代码
减少访问次数
合并代码(合并js等资源)
SSR服务端渲染(把资源在后端一次组装完成,减少客户端的请求次数)
缓存(静态资源加hash后缀,根据文件计算hash。文件不变,则会自动触发http缓存机制,返回304)
使用更快的网络 - CDN
让渲染更快
CSS放在head,JS放在body最下面
尽早开始执行JS,在DOMContentLoaded触发
懒加载(图片懒加载,上滑加载更多...)
对DOM查询进行缓存
避免频繁的DOM操作,使用DocumentFragement片段
节流、防抖
- 手写
节流
和防抖
防抖,节流的意思
防抖:频繁操作,最后XXms后触发
节流:频繁操作,按一定频率触发
// 节流
// 主要思想: 在频繁的回调函数中,每XXms就执行一次,平均速率
function throttle(fn,delay = 500) {
let timer = null
return function () {
if(timer){
return
}
timer = setTimeout(()=>{
fn.apply(this,arguments)
timer = null
},delay)
}
}
input.oninput = throttle(function (e) {
console.log(e);
},1000)
// 防抖 - 最后触发
// 主要思想: 在用户停止输入的那一瞬间开始,倒数XXms
function debounce(fn, delay = 1000) {
let timer = null
return function () {
if(timer){
clearTimeout(timer)
}
timer = setTimeout(()=>{
fn.apply(this,arguments)
timer = null
},delay)
}
}
input.oninput = debounce(function (e) {
console.log(e);
},1000)
安全
- Web前端常见的
安全攻击方式
和预防
常见的攻击方式
- XSS & XSRF
- XSS 发表博客,嵌入<script>脚本,拿到别人的cookie信息
解决方案:更换特殊字符<、>等等
- XSRF 跨站请求伪造 <img src=xxx.com/pay...../>
解决方案:使用post请求 & 增加验证(密码,验证码等等)
面试真题
1. var let const的区别
var 是ES5的语法 , let const是ES6的语法 ; var 有变量提升
var和let是变量 可以修改,const是常量,不可修改
let const有块级作用域,var没有
2. typeof 能判断那些数据类型
值类型:string,number,boolean,undefined,symbol
引用类型:object(注意,typeof null === 'object' <- null是值类型,但是浏览器bug)
函数:function
3. 列举 强制类型转换 & 隐式类型转换
强制:parseInt parseFloat toString等
隐式:if 、 逻辑运算 、 == 、 +拼接字符串
4. 手写深度比较 isEqual
function isObject(obj) {
return typeof obj === 'object' && obj !== null
}
function isEqual(obj1,obj2) {
// 不是对象 就直接返回两者三等
if(!isObject(obj1) || !isObject(obj2)){
return obj1 === obj2
}
if(obj1 === obj2){
return true
}
// 两个都是对象或者数组 而且不相等
let obj1keys = Object.keys(obj1)
let obj2keys = Object.keys(obj2)
if(obj1keys.length !== obj2keys.length){
return false
}
// 两个都是对象或者数组 而且keys个数一致
// 以obj1为基准 进行比较
for(let key in obj1){
let result = isEqual(obj1[key],obj2[key])
if(!result){
return false
}
}
return true
}
5. split() 和 join() 的区别
split是拆分字符串,join是拼接数组,两者完全背离
6. 数组常见API
---1.连接两个或更多的数组。
---2.检测数组元素的每个元素是否都符合条件。
---3.检测数组元素中是否有元素符合指定条件。
---4.反转数组的元素顺序。
---5.检测数组元素,并返回符合条件所有元素的数组。
---6.删除并返回数组的第一个元素。
---7.搜索数组中的元素,并返回它所在的位置。
---8.用于插入、删除或替换数组的元素。
---9.对数组的元素进行排序。
---10.删除数组的最后一个元素并返回删除的元素。
---11.把数组的所有元素放入一个字符串。
---12.把数组转换为字符串,并返回结果。
---13.向数组的末尾添加一个或更多元素,并返回新的长度。
---14.返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索。
---15.通过指定函数处理数组的每个元素,并返回处理后的数组。
---16.选取数组的的一部分。
---17.返回数组对象的原始值。
---18.向数组的开头添加一个或更多元素,并返回新的长度。
---19.数组求和
1.concat() 不
2.every() 不
3.some() 不
4.reverse() 会
5.filter() 不
6.shift() 会
7.indexOf() 不
8.splice() 会
9.sort() 会
10.pop() 会
11.join() 不
12.toString() 不
13.push() 会
14.lastIndexOf() 不
15.map() 不
16.slice() 不
17.valueOf() 不
18.unshift() 会
19.reduce((total,currentValue)=>{})
7. 数组slice和splice的区别是什么
slice是 切片
splice是 剪切
splice(1,3,x,x,x)
从下标为1开始,后截三位,替换成x,x,x,并返回截出来的值,为数组
8. [10,20,30].map(parseInt)
将其拆分开,就得到:
[10,20,30].map((item,index)=>{
// 注意parseInt的第二个参数,是代表进制
return parseInt(item,index)
})
9. 函数call和apply的区别
就在参数上是否是数组
10. 事件代理(委托)是什么
冒泡事件,无限下拉的点击事件监听
11. 闭包是什么?有什么特性?有什么负面影响?
闭包就是能够读取其他函数内部变量的函数
函数作为参数被传入,函数作为返回值被返回
理解 " 自由变量 " 是什么?
就是当前函数内找不到定义的变量,在父级作用域内,该变量就是自由变量
影响:
变量常驻内存,得不到释放,但不一定是内存泄漏(内存泄漏是不用的内存无法被回收,闭包占用的内存可能会使用)
12. 如何阻止" 事件冒泡 " 和 " 默认行为 "
阻止冒泡:e.stopPropagation()
取消默认行为:e.preventDefault()
13. jsonp的原理
通过script标签实现的
1. 在window上绑定一个逻辑函数
window.callback = function(data){
console.log(data)
}
2. 请求一个js资源,js资源里面有一个执行函数,callback({一些数据作为入参传递进来})
14. load 和 ready的区别
load 是所有资源加载完毕
ready => DOMContextLoaded 是dom加载完毕就会触发
15. 函数声明和函数表达式的区别
函数声明:在执行前预加载,类似代码提升
函数表达式:不会
16. new Object() 和 Object.create()的区别
{} === new Object({}) 原型是Object.prototype
Object.create(null) 没有原型
Object.create({xxx}) 可以指定原型
Object.create({xxx}) 把传入的对象赋值到__proto__上
17. 正则表达式
// 字符串 字母开头,后面数字字母下划线,长度6-30
/^[a-zA-Z]\w{5,29}$/
// 邮政编码
/^\d{6}$/
// 小写英文字母
/^[a-z]+$/
// 英文字母
/^[a-zA-Z]+$/
// 日期格式
/^\d{4}-\d{1,2}-\d{1,2}$/
// 用户名
/^[a-zA-Z]\w{5,29}$/
// 简单ip地址匹配
/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
18. 手写trim方法,保证兼容性
String.prototype.trim = function () {
return this.replace(/^\s+/,'').replace('/\s+$/','')
}
19. 获取数组的最大值
1. 将arguments变为数组,除了解构外,还有什么办法
let arr = Array.prototype.slice.call(arguments)
2. 使用原生获取最大值的方法
Math.max(n1,n2,n3...)
Math.min(n1,n2,n3...)
20. 如何使用JS实现继承
class继承
prototype继承(不推荐)
21. 如何捕获JS程序的异常
手动:try{}catch(e){}
自动:window.onerror = function(){
// 对跨域的js,cdn等,不会有详细
// 对于压缩的js,还需要配合 sourceMap 去反查询压缩的行列
}
22. 什么是JSON
就是一种数据格式,本身是一段字符串
格式和js对象解构一致,比较友好
23. 怎么得到浏览器参数
function query(name) {
let search = location.search.substr(1)
let reg = new RegExp(`(^|&)${name}=([^&]*)($|&)`)
let res = search.match(reg)
if(!res){
return null
}
return res[2]
}
24. 数组拍平
let arr = [1,2,[1,2]]
Array.prototype.concat.apply([],arr) // 两层
function flat(arr) {
let isDeep = arr.some(item=>item instanceof Array)
if(!isDeep){
return arr
}
let res = Array.prototype.concat.apply([],arr)
return flat(res)
}
console.log(flat([1, 2, [3, 4, [1, 2, [3, 4]]]]));
25. 数组去重
传统方式
挨个比较
function clear(arr){
let result = []
arr.forEach(a=>{
if(!result.includes(a)){
result.push(a)
}
})
return result
}
使用Set
new Set([1,1,2,3,1]) // 1,2,3
26. 介绍一下RAF requestAnimationFrame
动画的一个api,动画流畅的必要条件:60帧/s,使用setTimeout的话,需要手动控制,但是RAF是自动计算的
window.requestAnimateFrame(fn)
切换tab页面的时候,动画是暂停的,浏览器做了优化
27. Object.assign()是深拷贝吗?
不是的,只是浅层级被拷贝了,深层级没有
网友评论