CSS+HTML
1、H5新增特性
- Canvas -- 用于绘画的元素
- video、audio -- 用于播放视频和音频的媒体。
- 语义化标签 :
headernavmainarticlesectionasidefooter - webSocket -- 单个TCP连接上进行全双工通讯的协议。
- localStorage、sessionStorage -- 本地离线存储。
- 新的表单控件 -- 如:date、time、email、url、search。
2、重绘&重排
- 重排(回流):当DOM变化,引起了元素形状大小等几何变化,浏览器会重新计算元素的几何属性,将其放置到正确位置,这个过程叫重排。
- 重绘:当一个元素的外观发生变化但并未引起布局上的变化,重新把元素绘制出来对的过程叫做重绘
3、BFC
官方:块级格式上下文,是Web页面的可视CSS渲染的一部分,是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。浏览器对BFC的限制规则是
- 生成
BFC元素的子元素会一个接一个的放置。 - 垂直方向上他们的起点是一个包含块的顶部,两个相邻子元素之间的垂直距离取决于元素的margin特性。在
BFC-- 中相邻的块级元素的外边距会折叠(Mastering margin collapsing)。 - 生成
BFC元素的子元素中,每一个子元素左外边距与包含块的左边界相接触(对于从右到左的格式化,右外边距接触右边界),即使浮动元素也是如此(尽管子元素的内容区域会由于浮动而压缩),除非这个子元素也创建了一个新的BFC(如它自身也是一个浮动元素)。 触发条件: - 根元素,即
HTML标签 - 浮动元素:
float值为left、right overflow值不为visible,为auto、scroll、hiddendisplay值为inline-block、table-cell、table-caption、table、inline-table、flex、inline-flex、grid、inline-grid- 定位元素:
position值为absolute、fixed
我的理解:
内部的盒子会在垂直方向上一个接一个放置;垂直方向上的盒子间距margin决定,但是同属于一个BFC的两个盒子会发生margin重叠;每个盒子左右外边距不会超出包含他的块;BFC的区域不会与float的元素区域重叠;计算高度时浮动元素也要计算在内;
- 解决
margin重叠问题:给其中一个盒子添加float利用规则(BFC的区域不会与float的元素区域重叠) - 利用
BFC可以清除浮动:计算高度时浮动元素也要计算在内所以可以利用这一点清除浮动
4、编写CSS代码的时候如何优化CSS渲染性能?
- 内联首屏关键Css,优先加载主要的内容。
- 异步加载Css
//设置link标签media属性为noexis,浏览器会认为当前样式表不适用当前类型,会在不阻塞页面渲染的情况下再进行下载。加载完成后,将media的值设为screen或all,从而让浏览器开始解析CSS
<link rel="stylesheet" href="mystyles.css" media="noexist" onload="this.media='all'">
//通过rel属性将link元素标记为alternate可选样式表,也能实现浏览器异步加载。同样别忘了加载完成之后,将rel设回stylesheet
<link rel="alternate stylesheet" href="mystyles.css" onload="this.rel='stylesheet'">
- 合理使用选择器
5、CSS响应式布局有哪些?
- CSS3媒体查询@media 查询(麻烦,需要考虑的情况很多,不推荐)
- 在头部meta标签中加入“name=“viewport元标签””代表网页宽度默认等于屏幕宽度
- 借助bootstrap中的栅格
- 弹性盒子(推荐使用)
6、CSS盒模型(标准盒模型和IE盒模型)
共同点:两种盒模型都是由 content + padding + border + margin 构成,其大小都是由 content + padding + border 决定的,但是盒子内容宽/高度(即 width/height)的计算范围根据盒模型的不同会有所不同:
- 标准盒模型:只包含
content。 - IE盒模型:
content + padding + border。
box-sizing 改变元素的盒模型:
box-sizing: content-box:标准盒模型(默认值)。box-sizing: border-box:IE(替代)盒模型。
JS相关
1、闭包(面试+笔试===高频)
闭包是指有权访问另一个函数作用域中的变量的函数
- 优点:延长变量生命周期、私有化变量
- 缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏
function fun1() {
let n = 0;
function fun2() {
n++;
console.log("@"+n);
return n
}
return fun2;
}
let res = fun1();
// 执行12次,fn2中的变量的生命周期变长了,fun2函数中的n被外部使用了
for (let i = 0; i < 12; i++) {
console.log(res()+"执行的第"+(i+1)+"次");
}
2、原型、原型链(高频)
- 原型链:当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,这就是原型链。
- 原型链的尽头一般来说都是
Object.prototype所以这就是我们新建的对象为什么能够使用toString()等方法的原因。Object.prototype.toString.call([])//tostring可以返回一个精确的数据类型 - 原型:在
JS中,每当定义一个对象(函数)时,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype属性,这个属性指向函数的原型对象
3、this指向问题?
- 全局函数中,this指向window
- 作为对象的方法调用 this 指向调用对象
- 自定义构造函数中,this指向新的实例化对象(new 会改变 this 的指向)
- 事件绑定中this指向事件源
- 定时器函数中this指向window
4、JS继承的方式有哪些?
- 原型链的方式继承
- 借用构造函数来实现继承
- 组合继承
ES6中使用extends和super关键字来实现继承
5、JS事件循环机制或js的执行机制?
可参考此篇文章:一次弄懂Event Loop
EventLoop,就像是一个银行叫号系统,负责去找到对应任务队列中的函数,然后放入执行栈中进行调用,任务队列分为宏任务和微任务。在执行过程中,某个宏任务执行结束后,然后查看是否有微任务,如果没有,则执行下一个宏任务,以此类推直到全部执行结束。
js是一个单线程、异步、非阻塞I/O模型、 event loop事件循环的执行机制
所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。 同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。异步 任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程, 某个异步任务可以执行了,该任务才会进入主线程执行。
宏任务包含
script(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)
微任务
Promise.then
Object.observe
MutationObserver
process.nextTick(Node.js 环境)
请写出下段代码的输出结果(js事件循环机制)
new Promise(resolve => {
console.log(1);
setTimeout(() => console.log(2), 0)
Promise.resolve().then(() => console.log(3))
resolve();
}).then(() => console.log(4))
console.log(5)
//结果:1 5 3 4 2
6、原生ajax
ajax是一种异步通信的方法,从服务端获取数据,达到局部刷新页面的效果。
实现步骤:
- 创建XMLHttpRequest对象;
- 调用open方法传入三个参数 请求方式(GET/POST)、url、同步异步(true/false);
- 监听onreadystatechange事件,当readystate等于4时返回responseText;
- 调用send方法传递参数。
7、事件冒泡、委托(捕获)
-
事件冒泡指在在一个对象上触发某类事件,如果此对象绑定了事件,就会触发事件,如果没有,就会向这个对象的父级对象传播,最终父级对象触发了事件。
-
事件委托本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,并且父节点可以通过事件对象获取到目标节点,因此可以在父节点上定义监听函数,我们就可以到具体触发事件的元素,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件代理。
event.stopPropagation() 或者 ie下的方法 event.cancelBubble = true; //阻止事件冒泡
8、promise?(高频)
是异步编程解决的一种方案,可以浅显的认为他就是个容器,里面存放着未来才会结束的事情的结果。同时Promise也是一个对象,可以从该对象获取异步操作的消息。可以解决回调层层嵌套的问题。
promise有几种状态?三种
promise有三种状态pending(等待)、已完成(fullled)、已拒绝(rejected)- 一个
promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换
promise的方法有哪些?
promise.all-----同时多个异步操作时,同时发生,等待最后一个结束才结束。只要有一个出错,则都获取不到
const request1 => () => axios.get()
const request2 => () => axios.get()
const request3 => () => axios.get()
const request4 => () => axios.get()
Promise.all([request1(), request2(), request3(), request4()]).then(res => {
// res中就包含了四个请求得到的结果
})
promise.race-----可以在同时多个异步操作时,同时发生,第一个结束就结束 在一些异步处理中,我们想要设置超时时间的话,xhr对象可以调用xhr.abort()让请求结束,但是其他的没有
const asyncFn = () => new Promise(() => {
// 代码
})
Promise.race([asyncFn(), new Promise((resolve) => {
setTimeout(() => {
resolve()
}, 5000)
})])
- Promise.reject (返回一个失败的 promise)
- Promise.resolve (返回一个成功的 promise)
- Promise.prototype.finally (无论成功还是失败都会执行 finally)
- Promise.prototype.catch (就是调用 promise 自身的 then 方法,只传入失败的回调)
Async和await
- sync/await是生成器函数的语法糖 async/await是基于promise实现的,他们不能用于普通的函数回调 async/await更符合同步语义 使得异步代码看起来更像同步代码 async/await与promise一样,是非阻塞的 执行async函数返回的都是promise对象
9、函数防抖和节流(高频---笔试+面试)
函数防抖:在规定时间内多次执行代码,只执行最后一次(按钮频繁点击时,只让最后一次生效) 函数节流:定义一个执行频率时间,在执行的过程每隔对应的频率时间执行一次(表单验证中输入内容时、滚动条事件)
平时项目中我们会直接使用lodash库,来解决对应的问题
// 防抖
function debounce(func, wait) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context, args)
}, wait);
}
}
// 节流函数
function throttle(fn, delay) {
// 记录上一次函数触发的时间
var lastTime = 0;
return function() {
// 记录当前函数触发的时间
var nowTime = Date.now();
if (nowTime - lastTime > delay) {
// 修正this指向问题
fn.call(this);
// 同步时间
lastTime = nowTime;
}
}
}
document.onscroll = throttle(function() { console.log('scroll事件触发' + Date.now()) }, 200)
10、let;const;var区别?
不同点:
作用域:
- Var声明的变量只能是全局对的或者是函数块的
- let声明的变量它的作用域既可以是全局或者整个函数块
- var 允许在同一作用域中重复声明,而 let 不允许在同一作用域中重复声明,否则将抛出异常
- var 在全局环境声明变量,会在全局对象里新建一个属性,而 let 在全局环境声明变量,则不会在全局对象里新建一个属性。
- var 声明变量存在变量提升;let 声明变量存在暂存死区
相同点:
- var 和 let 的作用域规则都是一样的,其声明的变量只在其声明的块或子块中可用
11、JS中map和filter区别?
-
两者都可返回一个新的数组,并且不改变原数组
-
map返回的是每个元素执行return后的结果组成的一个新数组,而不是返回原数组中的元素组成的一个新的数组,新的数组长度和原数组是一致的;然而filter返回的是对原数组的过滤,其元素还是原数组中的元素,长度可能发生变化,但不改变原数组中的元素值。
let arr = [1, 2, 3, 4, 4, 3, 8, 9];
let filterArr=arr.filter((item,index,arr)=>{
console.log("当前元素值:",item,"当前元素索引:",index,"被遍历的数组",arr);
return item>4
//注意:此方法可以返回符合条件的元素组成的新的数组,此方法不会改变原数组
})
let mapArr=arr.map((item,index,arr)=>{
console.log("当前元素值:",item,"当前元素索引:",index,"被遍历的数组",arr);
//return item>4
//对原数组每个元素判断如果条件为真返回true否则返回flase
//结果:[false, false, false, false, false, false, true, true]
//当为return item+"mapArr"
//结果是['1mapArr', '2mapArr', '3mapArr', '4mapArr', '4mapArr', '3mapArr', '8mapArr', '9mapArr']
})
12、数组去重(重点掌握数组的方法)
filter、Arr.from+set、for循环+splice
-
filter 遍历数组,过滤出一个符合条件的新数组
let newArr = arr.filter((item,index)=>{return item>4})//筛选出原数组中值大于4的元素 console.log(newArr); -
Set是es6中提供的一种数据结构,它类似数组但与数组不同的是,它的值都是唯一的没有重复值.
- Set本质也是一个构造函数,因此在使用时需要new,同时Set可以接收一个数组(或者具有 iterable 接口的其他数据结构)作为参数用来初始化Set
- Set中的值可以是任意类型的,但必须不能重复
- Set的最大特点就是,里面的值都是唯一的,因此可以用来进行数组去重使用
- Set中认为NaN和NaN是同一个值,因此Set中只能有一个NaN值(但我们知道事实上NaN和NaN用于是不相等的)
- Set中两个对象永远是不相等的,即使键和值都是一样的
- Set也可以为字符串去重
- 在向Set添加值的时候不会发生类型转换
- Set 是可遍历的
let arr = [1,1,1,2,3,4,4,5,6,7,8,8,8] arr = Array.from(new Set(arr))// 或者 arr = [...new Set(arr)] //输出结果:[1,2,3,4,5,6,7,8]
13、ES6常用的
(1)Let和Const和var
- 用来声明变量,与var不同的是,let和const会创建一个块级作用域(花括号包裹的可称之为一个块级作用域)
- 在for循环中let和const会每循环一次就生成一个块级作用域,而且for循环会给下一个变量重新赋值
- var、let、const都有变量提升,不同的是var在创建的时候就会给变量初始化值(undefined),而let和const只有当声明的语句执行了才会初始化并赋值,而这时const是必须得赋值。创建到初始化之间的代码块叫就形成了暂时性死区。
- const声明的变量是常量(不会改变的变量),如果声明的是引用类型,则不能改变其引用地址。
(2)箭头函数
箭头函数和function声明的普通函数区别
- 箭头函数没有Protype属性不能使用new关键字调用
- 没有自己的this
14、基本数据类型和引用数据类型的区别
Number String Boolean Null Undefined Symbol
原始类型存储在栈内存中,修改对应的值,值会被覆盖。
引用类型存储在堆内存中,在栈内存中只是一个地址。
15、数据类型判断(掌握)?
16、cookie,localstorage, sessionstrorage 之间有什么区别?
相同点:存储在客户端
不同点
- 与服务器交互: cookie 是网站为了标示用户身份而储存在用户本地终端上的数据(通常经过加密) cookie 始终会在同源 http 请求头中携带(即使不需要),在浏览器和服务器间来回传递 sessionStorage 和 localStorage 不会自动把数据发给服务器,仅在本地保存
- 存储大小: cookie 数据根据不同浏览器限制,大小一般不能超过 4k ,sessionStorage 和 localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或 更大
- 有效期时间: localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据 sessionStorage 数据在当前浏览器窗口关闭后自动删除 cookie 设置的cookie过期时间之前一直有效,与浏览器是否关闭无关
常见Jquery面试题
1、jquery中$()是什么?
() 函数,它会返回一个包含所有匹配的 DOM 元素数组的 jQuery 对象。
2、Jquery中的选择器?
ID选择器、类选择器、标签选择器等
$('#LoginTextBox') //ID选择器
$('.active')//类选择器
$("div")//标签选择器
3、如何在点击一个按钮时使用 jQuery 隐藏一个图片?
思路:为按钮设置事件并执行hide() 方法
$('#Button').click(function(){
$('#ImageToHide').hide();
});
4、 $(document).ready() 是个什么函数?为什么要用它?(重要)
ready() 函数用于在文档进入ready状态时执行代码。当DOM 树完全加载(例如HTML被完全解析DOM树构建完成时),jQuery才允许执行代码。使用$(document).ready()的最大好处在于它适用于所有浏览器,解决了跨浏览器的难题。
5、 JavaScript window.onload 事件和 jQuery.ready 函数有何不同?
前者,需要等待dom树加载完毕以及外部资源完全加载完毕后才会执行后续代码
后者,只需要对dom树进行等待
对比:使用 jQuery $(document).ready() 的优势是你可以在网页里多次使用它,浏览器会按它们在 HTML 页面里出现的顺序执行它们,相反对于 onload 技术而言,只能在单一函数里使用。
6、如何找到所有 HTML select 标签的选中项?
原理:利用属性选择器和 :selected 选择器,结果只返回被选中的选项
<select name="n_select" class="c_select" id="i_select1">
<option value="1">1</option>
<option value="2">2</option>
<option value="3" selected>3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
<scrpit>
$('[name=NameOfSelectedTag] :selected')
$('.c_select option:selected');
$('.c_select').find('option:selected');
//获取选中的值
$('[name=NameOfSelectedTag] :selected').text()或者......val()
</scrpit>
7、jQuery 里的 each() 是什么函数?你是如何使用它的?
解释:遍历一个元素的集合
//遍历数组
$(function () {
$.each([52, 97], function(index, value) {
alert(index + ': ' + value);//0:52 1:97
});
})
//遍历对象
$(function () {
var obj = {
"name": "张三",
"age": 18
};
$.each( obj, function( key, value ) {
alert( key + ": " + value );//name:张三 和 age:18
});
})
//遍历一个元素
$(function () {
$.each({ name: "张三", age: 19 }, function( k, v ) {
alert("键:"+ k ,"值:"+v );//键:name,值:张三 和 键:age,值19
});
})
8、你是如何将一个 HTML 元素添加到 DOM 树中的?
- append() - 在被选元素的结尾插入内容
- prepend() - 在被选元素的开头插入内容
- after() - 在被选元素之后插入内容
- before() - 在被选元素之前插入内容
9、$(this) 和 this 关键字在 jQuery 中有何不同?
$(this) 返回一个 jQuery 对象,可以对它调用多个 jQuery 方法,比如用 text() 获取文本,用val() 获取值等等。而 this 代表当前元素,它是 JavaScript 关键词中的一个,表示上下文中的当前 DOM 元素。
10、如何使用jQuery来提取一个HTML 标记的属性 例如. 链接的href?
首先需要利用jQuery选择及选取到所有的链接或者一个特定的链接,然后你可以应用attr()方法来获得他们的href属性的值
$('a').each(function(){
alert($(this).attr('href'));
});
11、如何使用jQuery设置一个属性值?
attr()方法和jQuery中的其它方法一样,能力不止一样. 如果你在调用attr()的同时带上一个值 例如. attr(name, value), 这里name是属性的名称,value是属性的新值
12、jQuery中 detach() 和 remove() 方法的区别是什么?
- detach() 方法移除被选元素,包括所有的文本和子节点。然后它会保留数据和事件。
- remove() 方法移除被选元素,包括所有的文本和子节点。该方法也会移除被选元素的数据和事件。
13、利用jQuery来向一个元素中添加和移除CSS类?
利用 addClass() 和 removeClass() 这两个 jQuery 方法。动态的改变元素的class属性可以很简单例如. 使用类“.active"来标记它们的未激活和激活状态
14、使用 CDN 加载 jQuery 库的主要优势是什么?
- 报错节省服务器带宽以及更快的下载速度
- 如果已有缓存的库就不会再次下载
15、 jQuery.get() 和 jQuery.ajax() 方法之间的区别是什么?
ajax() 方法更强大,更具可配置性, 让你可以指定等待多久,以及如何处理错误。get() 方法是一个只获取一些数据的专门化方法。
16、你要是在一个 jQuery 事件处理程序里返回了false会怎样?(可以阻止事件冒泡)
Vue框架
1. 跨域是什么?如何解决跨域?
由于浏览器的同源策略导致,前端请求后台接口时,如果协议、域名、端口三者有一个不同则会产生跨域问题。
JSONP(前端)
借助于script的src没有跨域问题,利用src引入对应的函数的调用,函数的实参就是对应的数据;但是只能进行 GET 请求,无法实现上传数据等操作。
实现原理:
-
利用
script标签,规避跨域,<script src="url">。 -
在客户端声明一个函数,
function jsonCallback() {}。 -
在服务端根据客户端传来的信息,查找数据库,然后返回一份字符串。
-
客户端,利用
<script>标签解析为可运行的JavaScript代码,调用jsonCallback()函数。
CORS(后台)
后台可以去设置请求头,可以允许某个域名下的某种请求方式跨域请求。
response.writeHead(200, {
'Access-Control-Allow-Origin': '*
}
一般情况下,在开发环境中,开启所有域名和请求方式,生产环境根据需求指定特定的域名。
代理
开发环境代理
基于vue的开发环境,我们可以去配置devServer里的proxy。根据我们后端提供的结构,进行对应的代理操作 vue.config.js
module.exports = {
devServer: {
proxy: '要代理的后台地址'
}
}
| 原接口 | 现地址 |
|---|---|
| http://localhost:3000/users | http://localhost:8080/users |
有些时候我们需要对特定前缀的接口进行代理
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
ws: true,
changeOrigin: true
},
'/foo': {
target: 'http://localhost:4000'
}
}
}
}
| 原接口 | 现地址 |
|---|---|
| http://localhost:3000/api/users | http://localhost:8080/api/users |
| http://localhost:4000/foo/xxx | http://localhost:8080/foo/xxx |
如果后台在开发接口的过程中没有给我们添加公共前缀
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
ws: true,
changeOrigin: true,
pathRewrite: {
'^/api': '',
}
},
'/foo': {
target: 'http://localhost:4000',
ws: true,
changeOrigin: true,
pathRewrite: {
'^/foo': '/aaa',
}
}
}
}
}
| 原接口 | 现地址 |
|---|---|
| http://localhost:3000/users | http://localhost:8080/api/users |
| http://localhost:4000/aaa/xxx | http://localhost:8080/foo/xxx |
生产环境
当开发完成后,没有服务了,就不能靠devServer的代理,我们可以使用nginx代理
server {
listen 90;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
// 代理配置 要让什么样的前缀代理到哪个地址
location /api {
proxy_pass http://localhost:3000;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
2. 异步的解决
回调函数
Promise
Generator
async/await 是上面的语法糖
基于Promise
3. 如果后台直接返回10万条数据,前端怎么优化
在项目中这个操作是不允许,一般我们是要求后台做分页的
前端在请求的过程中,如果后台没有提供对应的参数我们没有办法优化请求过程,前端能做的优化是前端渲染部分,不能直接把10w条数据直接渲染。可以每次只取其中的n条,渲染在页面上,做分页加载,或者滚动加载
在前端定义两个参数,一个page,一个是limit
根据这两个参数从10w条数据中,去得到对应的列表
for(let i = (page - 1) * limit; i < page * limit - 1; i++) {
10w条数据[i]
}
4. vue2数据响应式原理
(不兼容IE8)
响应式:当数据改变,页面自动渲染。
想要实现这个功能就要监听数据的改变
在Vue2里利用Object.defineProperty可以给对象中的每个属性添加getter和setter方法。所谓的setter的目的,就是我们在设置数据时可以监听到数据的改变。监听到数据的改变后,就可以通知对应的watcher,watcher去调用页面的render函数,触发页面的渲染
Object.defineProperty
let name = "张三"
const stu = {
name
}
// 这样写不能监听数据的变化
// 可以利用Object.defineProperty
let name = "张三"
const stu = {}
Object.definProperty(stu, "name", {
set
})
4. Vue3的数据响应式原理
(Vue3不兼容IE)
Vue3中利用的proxy,给数据添加拦截,当我们要修改数据时,可以触发对应的set函数,所谓的setter的目的,就是我们在设置数据时可以监听到数据的改变。监听到数据的改变后,就可以通知对应的watcher,watcher去调用页面的render函数,触发页面的渲染
5. 深浅拷贝
//浅拷贝:是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象;浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响。
//深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象;深拷贝:从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。
深拷贝、浅拷贝、赋值的区别
我的理解: 深拷贝不仅把内容都拷贝到了,而且还更换了内存地址,所以前后不会有影响;浅拷贝,拷贝的是内存地址和值,所以会相互影响。但是用const声明的常量要特别注意,重复赋值会报错。
// 浅拷贝
let obj1 = {
name : '浪里行舟',
arr : [1,[2,3],4],
};
let obj3=shallowClone(obj1)
obj3.name = "阿浪";
obj3.arr[1] = [5,6,7] ; // 新旧对象还是共享同一块内存
// 这是个浅拷贝的方法
function shallowClone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
console.log('obj1',obj1) // obj1 { name: '浪里行舟', arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj3',obj3) // obj3 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
// 对象赋值
let obj1 = {
name : '浪里行舟',
arr : [1,[2,3],4],
};
let obj2 = obj1;
obj2.name = "阿浪";
obj2.arr[1] =[5,6,7] ;
console.log('obj1',obj1) // obj1 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj2',obj2) // obj2 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
// 深拷贝
let obj1 = {
name : '浪里行舟',
arr : [1,[2,3],4],
};
let obj4=deepClone(obj1)
obj4.name = "阿浪";
obj4.arr[1] = [5,6,7] ; // 新对象跟原对象不共享内存
// 这是个深拷贝的方法
function deepClone(obj) {
if (obj === null) return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (typeof obj !== "object") return obj;
let cloneObj = new obj.constructor();
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key]);
}
}
return cloneObj;
}
console.log('obj1',obj1) // obj1 { name: '浪里行舟', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj4',obj4) // obj4 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
深拷贝浅拷贝实现方法
浅拷贝
- 1.
Object.assign() - 2.
函数库lodash的_.clone方法 - 3.展开运算符...
深拷贝
-
函数库
lodash的_.cloneDeep方法 -
jQuery.extend()方法$.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝 var $ = require('jquery'); var obj1 = { a: 1, b: { f: { g: 1 } }, c: [1, 2, 3] }; var obj2 = $.extend(true, {}, obj1); console.log(obj1.b.f === obj2.b.f); // false -
手写递归方法
递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
深拷贝
var obj = {
name: "test",
desc: "origin",
sendobj: {
name: "test2",
desc: "origin2"
}
}
function copy(obj) {
let newobj = null // 接受拷贝的新对象
if (typeof (obj) == 'object' && typeof (obj) !== null) { // 判断是否是引用类型
newobj = obj instanceof Array ? [] : {} // 判断是数组还是对象
for (var i in obj) {
newobj[i] = copy(obj[i]) // 判断下一级是否还是引用类型
}
} else {
newobj = obj
}
return newobj
}
var obj1 = copy(obj)
obj1.sendobj.name = "change"
console.log(obj1);
console.log(obj);
console.log(obj1.sendobj.name)//change
console.log(obj.sendobj.name);//test2
6 排序算法
-
冒泡排序
依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
//从小到大排序
const arr = [5, 2, 7, 8, 34, 7, 39, 12, 56, 9, 1]
function Sort(arr) {
// 外层循环i控制比较的轮数
for (let i = 0; i < arr.length; i++) {
// 里层循环控制每一轮比较的次数j,arr[i] 只用跟其余的len - i个元素比较
for (let j = 1; j < arr.length - i; j++) {
// 若前一个元素"大于"后一个元素,则两者交换位置
if (arr[j - 1] > arr[j]) {
[arr[j - 1], arr[j]] = [arr[j], arr[j - 1]]
}
}
}
return arr
}
console.log(Sort(arr)) // [1, 2, 5, 7, 7, 8, 9, 12, 34, 39, 56]
-
插入排序
插入排序是指在待排序的元素中,假设前面n-1(其中n>=2)个数已经是排好顺序的,现将第n个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第n个数的这个序列也是排好顺序的。按照此法对所有元素进行插入,直到整个序列排为有序的过程,称为插入排序。
//将arr[]按升序排列,插入排序法
function insertSort(arr) {
for (let i = 1; i < arr.length; i++) {
//将arr[i]插入到arr[i-1],arr[i-2],arr[i-3]……之中
for (let j = i; j > 0; j--) {
if (arr[j] < arr[j - 1]) {
[arr[j - 1], arr[j]] = [arr[j], arr[j - 1]]
}
}
}
return arr
}
7 查找算法
- 顺序查找
- 二分查找
10、虚拟DOM
虚拟DOM就是用一个JS对象来模拟一个DOM对象的操作,最终虚拟DOM还是要被转换成真实DOM
虚拟DOM可以提升页面中列表修改时的DOM渲染性能,主要带来的好处是可以通过虚拟DOM渲染成所有的其他的颜色的UI组件比如安卓 IOS,赋予了js开发原生APP的能力。
11、 Vuex是什么,你们在项目中如何使用vuex
Vuex用来简化数据通信,是一个集中式的状态管理,用于存储我们页面中的数据
我们在使用Vuex时,基于路由组件为模块,我们在对应的模块中,去管理我们对应的页面的所有数据,数据存放在state,修改数据使用mutation,获取数据使用action
VueX的五个核心
state 存储对应的数据
mutations 修改state的数据
actions 异步获取数据然后commit给mutation
getters 从state派生出新的数据
modules 用于模块的划分
12、axios请求相关
所有请求都携带token怎么做
使用axios的请求拦截器,在config中设置对应的headers.token为我们登录后拿到的token。
// 封装axios
import axios from "axios";
import { Message } from "element-ui";
import router from "../router";
// 通过create方法,创建一个新的axios对象
const instance = axios.create({
baseURL: "http://localhost:3000/", // 未来公共接口地址是什么,就写什么
timeout: 5000, // 设置超时时间
});
// 请求拦截器
instance.interceptors.request.use(
(config) => {
const token = localStorage.getItem("token");
if (token) {
config.headers.authorization = token;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
instance.interceptors.response.use(
(response) => {
// 想这么写一定要和后台进行沟通
if (response.data.code === 200) {
// 请求成功 方便我们获取请求到的数据 不需要再res.data.data
if (response.headers["x-total-count"]) {
return {
data: response.data.data,
total: +response.headers["x-total-count"],
};
} else {
return response.data.data;
}
} else {
// 当接口中有错误时,直接显示错误信息,不需要在每次请求的时候都去判断错误,然后显示错误信息
Message.error(response.data.msg);
return Promise.reject(response.data.msg);
}
// return response
},
(error) => {
console.log([error]);
// 判断错误代码是不是401
if (error.response.status === 401) {
Message.error("token失效");
// 跳转到登录
router.push("/login");
}
return Promise.reject(error);
}
);
export default instance;
登陆验证流程
- 在组件中引入封装好的axios对象,creat一个axios对象并设置baseurl和超时时间
- 设置请求拦截器,request.use,判断config中是否有token,有的话returnconfig,没有的话抛出promise错误信息
- 设置响应拦截器:response.use,请求成功获取到请求的数据,否则抛出erro错误信息
- 导出创建的axios对象
token放在localstorage里安全么?
不安全,但是后台也会对前端传递的token进行验证,不通过返回401
如何做统一的错误处理
利用响应拦截器,判断对应的错误代码,做出对应的相应错误判断
13、 React 类组件和函数组件的区别
- 类组件有状态和生命周期, 函数式组件没有(现在可以利用hook来解决对应的问题)
- this指向的问题,所有的状态及函数的使用,都需要使用this.事件绑定中,this指向有问题,需要用.bind 函数式组件没有
- 类组件是个class 函数式组件是一个function
- 函数式组件渲染的速度更快
14、为什么vue和react在渲染列表都需要添加key
添加key是为了提高对列表操作的性能。
key的作用是用来优化虚拟DOM的diff算法的。在修改列表中某个值时,没有key的话,虚拟DOM需要把新的数据重新的生成虚拟DOM结构,然后替换到原先列表的位置。如果有key可以直接找到对比后不一样的虚拟节点进行修改。
15、Vue路由拦截(某些页面需要登陆才可访问)
Vue 路由拦截(对某些页面需要登陆才能访问) - 阿泽码农 - 博客园 (cnblogs.com)
16、http状态码(了解)
2XX 成功
- 200 OK,表示从客户端发来的请求在服务器端被正确处理
- 204 No content,表示请求成功,但响应报文不含实体的主体部分
- 206 Partial Content,进行范围请求
3XX 重定向
- 301 moved permanently,永久性重定向,表示资源已被分配了新的 URL
- 302 found,临时性重定向,表示资源临时被分配了新的 URL
- 303 see other,表示资源存在着另一个 URL,应使用 GET 方法丁香获取资源
- 304 not modified,表示服务器允许访问资源,但因发生请求未满足条件的情况
- 307 temporary redirect,临时重定向,和302含义相同
4XX 客户端错误
- 400 bad request,请求报文存在语法错误
- 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息
- 403 forbidden,表示对请求资源的访问被服务器拒绝
- 404 not found,表示在服务器上没有找到请求的资源
5XX 服务器错误
- 500 internal sever error,表示服务器端在执行请求时发生了错误
- 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求
17、为啥有的请求之前会自动有一个options请求
当我们的后台通过cors解决跨域时,发POST请求会遇到options请求。
先发一个options请求目的是为了确保接口可以正常请求,只有options请求成功了,才有继续发送post请求
18、什么是三次握手
TCP建立连接要经历三次握手
第一次
第一次握手:建立连接时,客户端发送syn包(seq=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次
第二次握手:服务器收到syn包,必须确认客户端的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
第三次
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。 ****
19、什么是四次挥手
对于一个已经建立的连接,TCP使用改进的四次挥手来释放连接(使用一个带有FIN附加标记的报文段)。TCP关闭连接的步骤如下:
第一步,当主机A的应用程序通知TCP数据已经发送完毕时,TCP向主机B发送一个带有FIN附加标记的报文段(FIN表示英文finish)。
第二步,主机B收到这个FIN报文段之后,并不立即用FIN报文段回复主机A,而是先向主机A发送一个确认序号ACK,同时通知自己相应的应用程序:对方要求关闭连接(先发送ACK的目的是为了防止在这段时间内,对方重传FIN报文段)。
第三步,主机B的应用程序告诉TCP:我要彻底的关闭连接,TCP向主机A送一个FIN报文段。
第四步,主机A收到这个FIN报文段后,向主机B发送一个ACK表示连接彻底释放。
20、Vue和React路由的原理
都有两种模式 hash模式和history模式,让url改变,不改变访问的页面,通过url中内容变化,让js渲染不同的内容到页面上
hash模式就是改变#后面的内容,js检测到url的改变后,渲染不同的内容到页面上
history模式利用html5的hisotry.pushState让url改变,但是访问的页面不变,原理同上
21、例举出你所知的在VUE里动态绑定单个及多个class样式的方法?
<div :class="className"></div>//直接绑定
<div :class="{className: Boolean}"></div>//对象的方法
<div :class="{className: Boolean, className2: Boolean}"></div>//对象的方法
<div :class="['box', {className: Boolean}]"></div>//数组的对象的方法
22、修改vue里的数组或对象后然而页面没有更新,你是怎么解决的?(重点)
出现的原因:如果我们要对对象进行遍历,显示到页面,这个时候对象中新增的属性,由于在Vue2中我们是通过Object.defineProperty实现数据响应式也就是说给对象加get和set方法,但是这俩并不能监听到对象新增的属性,所以会出现以上情况。
解决方案:
- 如果是给对象添加少量的新属性,可以直接采用Vue.set()
//对于直接修改数组中的属性时无法实现响应式-------解决方法
var vm = new Vue({
data: {
arr: ['a', 'b', 'c']
}
})
vm.arr[1] = 'x' // 不是响应性的(直接修改数组中某个元素的值)
vm.arr.length = 4 // 不是响应性的
// Vue.set
Vue.set(vm.arr, indexOfItem, newValue)
// this.$set
vm.$set(vm.arr, indexOfItem, newValue)
//对于对象中新增或移除属性时无法实现响应式-------解决方法
var vm = new Vue({
data:{
age:35
}
})
vm.child = 2// vm.child 是非响应式的(新增一个child属性)
Vue.set(vm.非根节点对象, 键, 值)
this.$set(this.非根节点对象,键,值)
- 如果需要为新对象添加大量的新属性,则通过Object.assign()创建新对象
- 如果你实在不知道怎么操作时,可采取$forceUpdate()进行强制刷新 (不建议)
23、不同的页面有不同的 title 用单页面应用可以解决吗? 怎么解决!
我们在路由守卫中,获取到当前的路由信息,从路由信息中获取对应的页面的标题,使用document.title = 标题解决问题。需要我们手动的给每一个路由配置添加title。
24、组件中的data为什么是个函数,而不是一个对象?
每个Vue组件都是一个实例,共享data的属性,当data中是引用数据类型时,修改一个data就会影响所有的数据变化。
25、在登录注册密码加密操作
我们使用的md5加密,前端传递过去的内容是经过md5加密的,后台拿到数据库中也是加密的密码和我的密码进行比对,如果一样则登录成功
26、Vue中Computed和Watch的区别(重要)
- computed用来监控自己定义的变量,该变量不在data里面声明,直接在computed里面定义,然后就 可以在页面上进行双向数据绑定展示出结果或者用作其他处理,具有缓存特性(值如果不变化会复用)
- watch主要用于监控vue实例的变化,它监控的变量必须在data里面声明才可以,它可以监控一个 变量,也可以是一个对象,一般用于监控路由、input输入框的值特殊处理等等,它比较适合的场景是 一个数据影响多个数据,它不具有缓存性
- 计算属性不能执行异步任务,计算属性必须同步执行。
27、父子-子父-非父子通信方式(重要)
- 父->子:propos
<!-- vue是最大的根组件,其实我们这里的father是vue的子组件,son是father的子组件 -->
<div id="app">
<father></father>
</div>
<!-- 父组件模版内容 -->
<template id="father">
<div>
父组件:<input type="text" v-model="msg">
<hr>
<!-- info是我们自定义的属性名加上数据绑定,就可以接收到父组件inp框输入的数据了 -->
<son :info="msg"></son>
</div>
</template>
<!-- 子组件模版内容 -->
<template id="son">
<div>
<!-- 渲染接收到的数据info -->
<p>子组件得到父组件的数据是:{{ info }}</p>
</div>
</template>
<script>
Vue.component('father', {
//父组件
template: '#father',
data() {
return {
msg: "",
}
},
//定义局部组件的方式定义子组件
components: {
//子组件
son: {
template: "#son",
//props中的元素名必须和我们在父组件中自定义的属性名一致
props: ["info"]
}
}
});
new Vue({
el: "#app",
})
</script>
- 子->父:$emit
<div id="app">
<father></father>
</div>
<!-- 父组件 -->
<template id="father">
<div>
<p>父组件接收到的数据是:{{ msg }}</p>
<hr>
<!-- 自定义一个事件accept,当触发的时候就可以拿到传过来的数据 -->
<son @accept="accept" :msg="msg"></son>
</div>
</template>
<!-- 子组件 -->
<template id="son">
<div>
子组件:<input type="text" v-model="msg" placeholder="请输入你要发送的内容">
<!-- 点击按钮,发送子组件的数据 -->
<button @click="sendOut">发送数据</button>
</div>
</template>
Vue.component('father', {
template: '#father',
data() {
return {
msg: "",
}
},
methods: {
accept(value) {
this.msg = value;
}
},
components: {
son: {
template: "#son",
data() {
return {
msg: this.msg
}
},
methods: {
sendOut() {
// 通过this.$emit来触发accept事件,并将数据以参数(this.msg)的形式传给father
this.$emit('accept', this.msg);
},
},
}
}
});
new Vue({
el: "#app",
})
- 非父子:事件总线 emit(创建发出的事件不建议使用)
28、一个页面从输入URL 到页面加载显示完成,这个过程中都发生了什么?
- 浏览器地址栏输入url
- 浏览器会先查看浏览器缓存--系统缓存--路由缓存,如有存在缓存,就直接显示。如果没有,接着第三步
- 域名解析(DNS)获取相应的ip
- 浏览器向服务器发起tcp连接,与浏览器建立tcp三次握手
- 握手成功,浏览器向服务器发送http请求,请求数据包
- 服务器请求数据,将数据返回到浏览器
- 浏览器接收响应,读取页面内容,解析html源码,生成DOM树
- 解析css样式、浏览器渲染,js交互绑定多个域名,数量不限;
29、js模块化和组件是啥?
js模块化:具备特定功能的js文件,需要哪些功能就去拆分他并引入
组件:具备特定功能效果的代码集合
30、router 的区别是什么?
$route 是某个“路由信息对象”局部,包括 path,params,hash,query,fullPath,matched, name 等路由信息参数
router.go `方法
31、Vue路由传值的方式有哪几种?
Vue-router 传参可以分为两大类,分别是编程式的导航 router.push 和声明式的导航
1、router.push
- 字符串:直接传递路由地址,但是不能传递参数
this.$router.push("home")对象 - 命名路由 :这种方式传递参数,目标页面刷新会报错
this.$router.push({name:"news",params:{userId:123}) - 查询参数 :和 name 配对的式
params,path配对的是querythis.$router.push({path:"/news',query:{uersId:123}) - 接收参数
this.$route.query
2、声明式导航
- 字符串
<router-link to:"news"></router-link> - 命名路由
<router-link to:"{name:'news',params:{userid:1111}}"</router-link> - 查询参数
<router-link to:"{name:'/news',query:{userid:1111}}"></router-link>
32、导航解析流程
完整的导航解析流程:
- 导航被触发(/index=>/about)
- 在失活的组件(index)中调用
beforeRouteLeave守卫。 - 调用全局的
beforeEach守卫。 - 在复用的组件中调用
beforeRouteUpdate(如果有复用的话) - 调用路由独享守卫
beforeEnter - 解析异步路由组件
- 在被激活的组件中调用
beforeRouteEnter - 调用全局的
beforeResolve守卫(全局解析守卫) - 导航被确认
- 调用全局后置守卫
afterEach守卫 - 触发DOM更新
- 用创建好的组件实例调用
beforeRouteEnter守卫中传给next的回调函数。
33、路由守卫
分类:全局路由、单个路由独享、组件内部路由
- 每个路由守卫的钩子函数都有三个参数:to进入的目标路由、from离开自那个路由、next控制路由 在跳转时执行操作,必须执行
全局路由守卫
beforeEach(to,from, next)-----路由跳转前触发,常用于登陆验证beforeResolve(to,from, next)-----在beforeEach和 组件内beforeRouteEnter之后,afterEach之前调用afterEach(to,from)-----
全局路由直接挂载到 router 实例上
//全局验证路由
const router = createRouter({
history: createWebHashHistory(),
routes
});
// 白名单, 不需要验证的路由
const whiteList = ['/','/register']
router.beforeEach((to,from,next)=>{
if(whiteList.indexOf(to.path) === 0) {
// 放行,进入下一个路由
next()
} else if(!(sessionStorage.getItem('token'))){
next('/');
} else {
next()
}
})
34、Vue生命周期共分为几个阶段?
Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载 Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期
35、keep-alive(Vue 该如何实现组件缓存?)
组件化编程的过程中,频繁的组件切换,组件的实例都会重新创建。我们可以使用Vue内置的组件<Keep-alive>来缓存未被激活的组件,当再次激活此组件时,此组件会从缓存中快速渲染。
keep-alive本质上就是一个组件
在created的时候,初始化两个对象分别用于缓存虚拟DOM和VNode对应的健集合
在destory的时候,删除 this.cache 中缓存的VNode实例。
在mounted的时候,对include和exclude中的数据进行实时监听。然后实时更新或删除。
第一步:获取keep-alive包裹着的第一个子组件对象及其组件名;
第二步:根据设定的黑白名单(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例(VNode),否则执行第三步;
第三步:根据组件ID和tag生成缓存Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该key在 this.keys中的位置(更新key的位置是实现LRU置换策略的关键),否则执行第四步;
第四步:在 this.cache对象中存储该组件实例并保存 key 值,之后检查缓存的实例数量是否超过 max 的设置值,超过则根据LRU置换策略删除最近最久未使用的实例(即是下标为0的那个key)。
第五步:最后并且很重要,将该组件实例的 keepalive属性值设置为 true
36、路由传参
query传参-----将需要的参数以 【key = value】的方式放在URL地址中。
params动态传参-----在定义路由信息时,需要以占位符【:参数名】的方式将需要传递的参数指定到路由地址中
37、vue自定义指令?
微信小程序
1、微信小程序支付流程
- 客户端通过wx.login获取用户临时登录凭证code,发送到后端服务器换取openId
- 客户端将**
openId**以及相应需要的商品信息发送到后端 - 服务器在接收到商品Id、商品信息、openId后,经过系列算法,生成服务器订单数据
- 客户端接收返回的信息发起微信支付**
wx.requestPayment的参数**,发起微信支付
2、封装微信小程序的数据请求
- 封装axios请求,设置对应的baseurl和url
- 在api文件夹中创建所有请求的js文件,导入封装的axios
- 在页面中调用封装的请求方法
3、小程序页面间有哪些传递数据的方法?
- 给html元素添加data-*属性来传递值,然后通过e.target.dataset或onload的param参数获取(注:data-名称不能有大写字母、不可以存放对象)
- 设置id的方法标识来传值,通过e.currentTarget.id获取设置的id的值,然后通过设置全局对象的方式来传递数值
- 在navigator中添加参数数值
4、小程序和H5的区别
- 小程序加载速度更快(同样的服务器及外部环境下,网站需要加载代码,特别是有些网站js效果写的比较多,加载速度会更慢点)
- 功能强大(调用设备信息、摄像头等)
- 打开方式
- 安全性(小程序更好些)
笔试题:
1、this指向的问题
var myobj={
foo:"bar",
func:function(){
var self=this;
console.log(this);//myobj
console.log(this.foo);
console.log(self.foo);
(function(){
console.log(this);//window
console.log(this.foo);
console.log(self.foo);
}())
}
}
myobj.func()
2、手写一个简单的闭包
function fun1() {
let n = 0;
function fun2() {
n++;
console.log("@"+n);
return n
}
return fun2;
}
let res = fun1();
// 执行12次
for (let i = 0; i < 12; i++) {
console.log(res()+"执行的第"+(i+1)+"次");
}
3、封装一个求数组平均数的函数
var arr = [10,20,30,56,89];
function avg(array) {//封装求平均值函数
var len = arr.length;
var sum = 0;
for(var i = 0;i<len;i++){
sum +=array[i];
}
return sum/len;
}
console.log(avg(arr));
4、判断一字符串中某个字母出现的次数如: let str = "aabcdefgha"
let str = "aabcdefgha";
function getMost(str) {
//创建一个结果对象,暨最后返回的对象
var result = {};
//for...in...遍历,看str是否在result中,是的话让当前对象中某个key对应的值++,默认0开始加
for (let i in str) {
if (str[i] in result) {
result[str[i]]++;
} else {
// 如果是上述不满足,则让当前对象的中某个key的值等于1
var object = {};
object[str[i]] = 1;
result = Object.assign(result, object);//obj.assign浅拷贝,讲obj拷贝给result
}
}
return result;
}
var result = getMost(str);
console.log(result); //{a:3,b:1,c:1,d:1,e:1,f:1,g:1,h:1}
5、字符串翻转
6、数组转成树状结构
const SourceArr = [
{ id: '1', parentId: 0, name: '标题1' },
{ id: '1.1', parentId: '1', name: '标题1-1' },
{ id: '1.1.1', parentId: '1.1', name: '标题1-1-1' },
{ id: '2', parentId: 0, name: '标题2' },
{ id: '2.1', parentId: '2', name: '标题2-1' },
{ id: '2.2', parentId: '2', name: '标题2-2' },
{ id: '2.2.2', parentId: '2.2', name: '标题2-2-2' },
{ id: '3', parentId: 0, name: '标题3' },
]
function getData (arr) {
// filter实现
let res = arr.filter(item => {
item.children = arr.filter(etem => {
return item.id === etem.parentId
})
//过滤出一级标题组成新的数组
return item.parentId==0
})
return res
}
const TargetArr = getData(SourceArr)
console.log('TargetArr', TargetArr)
7、手写防抖和节流
优化问题:
1、网页首屏有图片,网页加载出现长时间空白
- 压缩优化图片,减少大小
- 占位图懒加载
2、图片懒加载是如何实现的?
在图片img标签上添加data-set属性(名字自拟比如:data-src),然后在图片的默认src上存放一张占位图(可以是低像素的原图),然后当浏览器窗口滚动到视图范围判断一下,如果是的话就将src的地址换成data-set的地址,这样就实现图片的懒加载。
小优化:由于浏览器的滚动条滚动事件会频繁触发,我们可以使用节流函数来解决
总结:
- 拿到所有的图片 dom
- 遍历每个图片判断当前图片是否到了可视区范围内。
- 如果到了就设置图片的 src 属性。
- 绑定 window 的
scroll事件,对其进行事件监听。
写到最后
这是自己结合网上的资源以及自己面试的时候遇到的总结,如果冒犯请联系本人,若有不足,还请大佬指正