前端面试高频基础技术面试题
- 原型和原型链
实例会继承构造函数的方法
//创建构造函数 function Word(words){ this.words = words; } Word.prototype = { alert(){ alert(this.words); } } //创建实例 var w = new Word("hello world"); w.print = function(){ console.log(this.words); console.log(this); //Person对象 } w.print(); //hello world w.alert(); //hello world
实例w拥有的–proto–属性===构造函数的prototype属性。并且实例的–proto–本身还有两个属性constructor和–proto–,而这个constructor指向构造函数本身。
原型链的最顶端就是Object.prototype ,原型链最重要的作用就是继承,实例来继承上一级的属性或方法
- 闭包
概念:声明在一个函数中的函数;内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。
特点:
外部访问函数内部变量成为可能
局部变量常驻内存中
避免使用全局变量,防止全局变量污染–解决方法是,在退出函数之前,将不使用的局部变量全部删除
造成内存泄漏(即:有一块内存空间被长期占用而得不到释放)
为什么要用闭包?因为有时候希望访问函数f1内部的变量,但是js做不到,所以通过在这个函数内部再次定义一个函数f2,那么这个函数就可以访问外部函数的变量,最后将这个函数内部f2作为返回值(一定用return将这个内部函数返回),那么在外部就可以访问到这个变量了。
- ES6新增
let和const
解构赋值
箭头函数
扩展运算符…
Array.from()
数组的find()和findIndex()、
字符串的includes()、startsWith()、endsWith()、repeat()方法
模板字符串
class语法糖类的创建
Promise处理异步
- 数组常用方法
push() pop() shift() unshift() slice() splice() join() concat() sort() reverse() forEach()lastIndexOf(num1,num2)–从num2开始查找num1最后出现的位置,lastIndexOf() 方法可返回一个指定的字符串值最后出现的位置,如果指定第二个参数 start,则在一个字符串中的指定位置从后向前搜索,该方法将从后向前检索字符串,但返回是从起始位置 (0) 开始计算子字符串最后出现的位置。 看它是否含有字符串。
- 字符串常用方法
indexOf() lastIndexOf() subString() slice() charAt(3)–返回指定索引的值,concat() replace() toUpperCase() toLowerCase()
- var let const各自的特点
let const 不会变量提升,块级作用域{},const声明的是常量,一旦声明不会改变,let const不能声明同名变量
- 事件委托
事件委托是将子元素的事件通过绑定在父元素的技巧。因为比如一个列表的li标签需要绑定点击事件,但是这个列表是请求数据动态渲染的,此时就要用到事件委托。利用的是DOM元素的事件冒泡。
比如:
var item1 = document.getElementById("goSomewhere");
var item2 = document.getElementById("doSomething");
var item3 = document.getElementById("sayHi");
document.addEventListener("click", function (event) {//addEventListener添加事件监听
var target = event.target;
switch (target.id) {
case "doSomething":
document.title = "事件委托";
break;
case "goSomewhere":
location.href = "http://www.baidu.com";
break;
case "sayHi": alert("hi");
break;
}
})
或者
<script>
var ul = document.querySelector("ul");
var li = document.querySelectorAll("li");
ul.onclick = function(e){//e指event,事件对象
var target = e.target || e.srcElement; //target获取触发事件的目标(li)
if(target.nodeName.toLowerCase() == 'li'){//目标(li)节点名转小写字母,不转的话是大写字母
alert(target.innerHTML)
}
}
</script>
————————————————
版权声明:本文为CSDN博主「demiling」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/demiling/article/details/83311102
事件委托可以减少操作DOM节点的次数,减少浏览器的重加载,提高代码性能。
- 深拷贝和浅拷贝
要理解深拷贝和浅拷贝就要知道数据在内存中储存位置。数据分为基本类型和引用类型,内存分为堆(又叫堆栈)、栈、全局静态区、只读区。深浅拷贝
栈:存放局部变量、形参、地址;特点是读取速度快,存数据压栈,取数据弹栈。
堆:存放引用数据类型的值,而这个引用数据类型的地址保存在栈中。特点是读取速度慢,会随着数据的增加而改变大小。
全局静态区:全局变量和静态变量,特点:在程序运行过程中,数据会一直在内存中
只读区:存放数据:常量区存放常量,代码区存放程序的代码(程序运行时是需要载入到内存中允许的,特点:此区域的数据在程序运行过程中肯定不能改变。
数据分为基本类型–值类型,引用类型–地址类型。这段代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<h1>深拷贝和浅拷贝</h1>
</body>
<script type="text/javascript">
function func1(){
var arr1=[1,2,3,4]
var arr2=arr1;
console.log("arr2为" + arr2)
arr2[0]=7
console.log(arr1)//7,2,3,4--说明改变arr2的值,arr1中的数据也改变了。是浅拷贝,拷贝的是地址,指向同一内存空间,取到同样的值。就像淘宝和京东都保存的是你家的地址,送货都是送到同一个地方。
}
var t = func1
t();
</script>
</html>
拷贝要分拷贝的东西是object与否;
由于数组是引用类型,简单的赋值arr2=arr1,其实进行的是浅拷贝,即只是拷贝了一份地址,arr1和arr2的地址是一样的,指向内存中的同一个数据,所以arr2在改变数据的时候,通过arr1的地址(是相同的),得到的数据也是改变的,这就是浅拷贝,因为数据都不独立。先看浅拷贝:只拷贝了键
var p2 = {};
for(let key in p){
p2[key] = p[key];
}
p2.books[0] ="四国";
console.log(p2);
console.log(p);
结果为
实现深拷贝的方式:键值都要拷贝
var p2 = {};
for(let key in p){
if(typeof p[key]=='object'){
p2[key]=[];//因为,我上面写的是数组,所以,暂时赋值一个空数组.
for(let i in p[key]){
p2[key][i] = p[key][i]
}
}else{
p2[key] = p[key];
}
}
p2.books[0] ="四国";
console.log(p2);
console.log(p);
更复杂的情形,json数据里又是json数据
var p = {
"id":"007",
"name":"刘德华",
"wife":{
"id":"008",
"name":"刘德的妻子",
"address":{
"city":"北京",
"area":"海淀区"
}
}
}
//写函数
function copyObj(obj){
let newObj={};
for(let key in obj){
if(typeof obj[key] =='object'){//如:key是wife,引用类型,那就递归
newObj[key] = copyObj(obj[key])
}else{//基本类型,直接赋值
newObj[key] = obj[key];
}
}
return newObj;
}
let pNew = copyObj(p);
pNew.wife.name="张三疯";
pNew.wife.address.city = "香港";
console.log(pNew);
console.log(p);
如果是数组,没有键值对,建议写一个复制自身的函数
Array.prototype.copyself = function(){
let arr = new Array();
for(let i in this){
arr[i] = this[i]
}
return arr;
}
在实际生产中,一般可以通过json.Parse和json.Stringfy来实现深拷贝。最后附上自己的代码,如果今后看不懂,看注释就清楚了
var singer = {
"name":"邓紫棋",
"age":"18",
"gender":"woman",
"songs":["喜欢你","泡沫","句号","再见"]
}
console.log(singer)
var copysinger = {}
for (let key in singer){//先循环原对象singer的所有key,接着判断每个key的类型是否为object
if(typeof singer[key]=="object"){//讨论是不是object,也即是songs这个数据
copysinger[key]=[]//说明找到songs,这个数组,所以拷贝出来的也应该是数组。
for(let i in singer[key]){//遍历songs数组中的每一个数据
copysinger[key][i]=singer[key][i]
}
}
else{//基本数据类型,直接拷贝{
copysinger[key]=singer[key]
}
}
如果json里面还有json,层层嵌套,那么就需要写一个递归函数来实现深拷贝,如:
var p = {
"id":"007",
"name":"刘德华",
"wife":{
"id":"008",
"name":"刘德的妻子",
"address":{
"city":"北京",
"area":"海淀区"
}
}
}
//写函数
function copyObj(obj){
let newObj={};
for(let key in obj){
if(typeof obj[key] =='object'){//如:key是wife,引用类型,那就递归
newObj[key] = copyObj(obj[key])
}else{//基本类型,直接赋值
newObj[key] = obj[key];
}
}
return newObj;
}
let pNew = copyObj(p);
pNew.wife.name="张三疯";
pNew.wife.address.city = "香港";
console.log(pNew);
console.log(p);
p因为wife,所以是json,wife因为有address所以也是json。
利用JSON.parse(JSON.stringify(object)) 这个方法简单暴力直接了当
let a = {
name: "syh",
book: {
title: "You Don't Know JS",
price: "45"
}
}
let b = JSON.parse(JSON.stringify(a));
console.log(b);
// {
// name: "syh",
// book: {title: "You Don't Know JS", price: "45"}
// }
a.name = "change";
a.book.price = "55";
console.log(a);
// {
// name: "change",
// book: {title: "You Don't Know JS", price: "55"}
// }
console.log(b);
// {
// name: "syh",
// book: {title: "You Don't Know JS", price: "45"}
// }
- JS中的堆和栈
栈:存放基本数据类型,形参和地址,速度快
堆:存放引用数据类型的数据
- this的指向问题
- JS数据类型
基本类型:除Object。 String、Number、boolean、null、undefined + Symbol
引用类型:object。里面包含的 function、Array、Date。
- HTTP协议–HTTP协议
DNS服务把域名解析成IP地址。当在浏览器网址栏输入一个URL时,DNS(域名服务器),会解析出URL中的域名,进而查询出对应的IP地址,浏览器根据这个IP地址与WEB服务器进行通信,而通信的协议就是HTTP协议。
三者之间的关系
- 浏览器缓存,包括哪几种以及各自的特点
浏览器缓存
- 垂直居中布局
第一种:absolute + margin自己最熟悉的一种 top:0 right:0 bottom:0 left:0;margin:auto;
.big{
width: 400px;
height: 400px;
background-color: red;
position: relative;
}
.small{
width: 100px;
height: 100px;
background-color: green;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
第二种:absolute + margin规定top:50%;left:50%再在margin-top:-50px;margin-left:-50px;
.big{
width: 400px;
height: 400px;
background-color: red;
position: relative;
}
.small{
width: 100px;
height: 100px;
background-color: green;
position: absolute;
top:50%;
left:50%;
margin-left: -50px;
margin-top: -50px;
top:50%;left:50%是固定写法;margin-left和margin-top是根据具体的外层盒子和内层盒子的大小具体计算出来的。
第三种:absolute + calc().利用calc()函数,calc(a - b),其中a是规定的大小,运算符 - 后面是在这个基础上减少的量 比如top:calc(50% - 50px)表示距离顶部50%,再减去50px;
.big{
width: 400px;
height: 400px;
background-color: red;
position: relative;
}
.small{
width: 100px;
height: 100px;
background-color: green;
position: absolute;
top:calc(50% - 50px);
left: calc(50% - 50px);
}
第四种:absolute + transform–translate() 根据左(X轴)和顶部(Y轴)位置给定的参数,从当前元素位置移动。
.big{
width: 400px;
height: 400px;
background-color: red;
position: relative;
}
.small{
width: 100px;
height: 100px;
background-color: green;
position: absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
- 清除浮动
浮动出现的原因:一个父级元素没有设置高度,里面的子元素使用了float浮动,导致父元素没有被撑开,上面成了一条线,这就是浮动。
浮动的影响:1.父元素的背景颜色和背景图片不能显示;2.边框不能被撑开;3padding和margin不能正确显示。
解决办法:
1.给父元素设置合适的高度height
2.clear:both;在父元素结束前增加一个
<style>clear{clear:both}</style>
<div class="divcss5">
<div class="divcss5-left">left浮动</div>
<div class="divcss5-right">right浮动</div>
<div class="clear"></div>
</div>
3.给父元素添加overflow:hidden
4.使用伪元素
.fa{
background-color: red;
}
.fa:after{
content:""; /*设置内容为空*/
height:0; /*高度为0*/
line-height:0; /*行高为0*/
display:block; /*将文本转为块级元素*/
visibility:hidden; /*将元素隐藏*/
clear:both; /*清除浮动*/
}
.fa{
zoom:1; /*为了兼容IE*/
}
- TCP三次握手四次挥手
详细解释三次握手,第一次:客户端向服务端发送TCP报文,表示想要链接服务器;第二次:服务器收到客户端发来的报文,并返回同意链接的报文;第三次:客户端知道同意连接,并发送报文给服务器,告诉我知道你同意我连接了。
为什么要进行第三次握手?防止服务器开启一些无用的链接增加服务器开销以及防止失效的连接请求报文又传送到了服务器因而产生错误。
四次挥手:第一次:客户端发送报文到服务器,表示想要释放连接;第二次:服务器发送报文给客户端,表示收到客户端想要释放连接的请求;第三次:还是服务器发送报文给客户端,表示已经准备释放链接了;第四次:客户端发送报文给服务器,表示接收到服务器准备释放链接的信号。
- 如何设置一个元素不可见
display:none;–控件直接消失
visibility:hidden;只是不可见 还占位。
<style type="text/css">
#box{
width: 200px;
height: 200px;
border: 1px solid black;
/* visibility: hidden; */
/* display: none; */
}
#box2{
width: 200px;
height: 200px;
background-color: red;
}
</style>
</head>
<body>
<h1>元素设置不可见</h1>
<div id="box"></div>
<div id="box2"></div>
</body>
效果
display:none
#box{
width: 200px;
height: 200px;
border: 1px solid black;
/* visibility: hidden; */
display: none;
}
#box2{
width: 200px;
height: 200px;
background-color: red;
}
</style>
</head>
<body>
<h1>元素设置不可见</h1>
<div id="box"></div>
<div id="box2"></div>
</body>
<script type="text/javascript">
var box = document.getElementById("box")
box.onclick=function (){
console.log(666)
}
效果挤上去了,说明不占位置
visibility:hidden
#box{
width: 200px;
height: 200px;
border: 1px solid black;
visibility: hidden;
/* display: none; */
}
#box2{
width: 200px;
height: 200px;
background-color: red;
}
</style>
</head>
<body>
<h1>元素设置不可见</h1>
<div id="box"></div>
<div id="box2"></div>
</body>
效果
- 什么是跨域,包括哪几种跨域
跨域是基于同域策略来讨论的,同源策略会阻止一个域的JavaScript和另一个域的内容进行交互,同源:协议、域名、端口号都相同。当前页面(一个url)跳转到另一个页面(url),其中协议、域名、端口号任何一个不相同既是跨域。解决跨域实现跨域访问的常用方法
1.JSONP跨域–原生实现
特点:简单实用,兼容性好(兼容IE低版本);缺点:仅支持get请求,不支持post请求
方法:通过向网页添加一个<script 发送请求 callback=dosomething> 服务器收到请求后,将返回的数据放在回调函数的参数里。
<script src="http://test.com/data.php?callback=dosomething"></script>
// 向服务器test.com发出请求,该请求的查询字符串有一个callback参数,用来指定回调函数的名字
// 处理服务器返回回调函数的数据
<script type="text/javascript">
function dosomething(res){
// 处理获得的数据
console.log(res.data)
}
</script>
2.JSONP跨域–jQuery ajax
$.ajax({
url: 'http://www.test.com:8080/login',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "handleCallback", // 自定义回调函数名
data: {}
});
3.JSONP跨域–Vue
this.$http.jsonp('http://www.domain2.com:8080/login', {
params: {},
jsonp: 'handleCallback'
}).then((res) => {
console.log(res);
})
2.CORS 解决跨域问题–是跨域资源共享的缩写,属于跨源 AJAX 请求的根本解决方法。
普通跨域请求:只需要服务端设置Access-Control-Allow-Origin
- 行内元素和块元素
块级元素:div , p , form, ul, li , ol, dl, form, address, fieldset, hr, menu, table
行内元素:span, strong, em, br, img , input, label, select, textarea, cite,
区别:
1.块级元素独占一行,行内元素会紧贴右边,直到一行装不下。
2.块级元素可以设置width,height。行内元素设置无效。
3.块级元素可以设置padding和margin,行内元素只有设置水平方向的才有效,竖直方向无效。
- VUE双向绑定原理
- VUE组件之间的传值
- VUE路由怎么传值
- forEach和map的区别
forEach一般是arr.forEach(function(){…})对数组中的每一个元素都执行一次函数,有forEach中的回调里有function(value,index,array)三个参数;当要取到数组中的每一个元素,并作相应的操作时可以用这个方法。
map返回一个新的数组,新数组中的元素是map里函数处理后的值。arr.map()不处理空数组,不改变原数组。map有一个参数function,这个函数参数可以有三个参数(value,index,arr)代表当前值,当前值下标,当前这个数组。
- VUE生命周期和作用
vue定义组件只能是item-to 小驼峰不行 大驼峰也不行
- 浮动带来的问题
1.父元素高度塌陷
2.脱离了文档流。浮动元素之后的普通元素无视浮动元素
- Promise
Promise看这篇就够了,还有自己代码里的笔记Promise是用来处理异步的。他的特点是1.对象的状态不受外界影响,三种状态pending(进行中) fulfilled(成功) rejected(失败)
2.一旦状态改变就不是再改变了。new一个Promise后里面的代码便会自动执行,所以一般把Promise写进一个函数,在需要的时候在调用,Promise是一个对象,有两个函数参数(resolve,reject)前者获得成功的参数,后者获得失败的参数,通过resolve和reject后,就该处理异步问题了(因为已经拿到数据了),这时是.then()一般默认在.then()里写成功的逻辑,但是失败的逻辑也在里面,.then()有两个函数参数,所以.then().reject()是错误写法–reject是包含在.then()里的,如果要处理错误应该写在catch里;.then().catch()。resolve的结果会传给.then()的第一个函数参数作为它的实参,reject的结果会传给.then()第二个函数参数的实参,既是下面代码中resolve的num传给了res,"数字大于10"传给了message。当然rejecte里的错误信息也可以直接由catch表达出来。
代码
function getData() {
return new Promise(function(resolve,reject){
// setTimeout(()=>{
// console.log(666)
// },2000)
//实际中得到的num可能是异步请求得到的数据
var num = Math.ceil(Math.random()*20)//取一个0--20的整数
if(num<=10){
//resolve保存异步请求到的数据,作为传给.then函数参数的实参。也就是后面的res等同于num
//message等同于reject的参数 也就是此处的"数字大于10"
resolve(num)
}else{
reject("数字大于10")
}
//在实际写代码中,一般默认.then()里面参数就是resolve,处理成功的逻辑
//但是其实.then()的参数有两个,还有一个reject来处理失败的逻辑,他们两个是并列同级的
//所以.then().reject()是错误写法 如果要处理错误逻辑 应该写在catch里,catch跟then同级
}).then((res)=>{//这个函数参数处理resolve 代表成功
console.log(res)
console.log(test)
},(message)=>{//这个函数参数处理reject代表失败
console.log(message+"大于10不行")
}).catch(()=>{
console.log("打印catch里语句说明有错误,catch可以处理resolve里的错误,比如使用了未定义的变量,使得错误进入catch,而不让程序在resolve出终结")
})
}
Promise.all()和Promise.race()
Promise.all([p1(),p2(),p3()]).then()意思是当三个p1,p2,p3都成功时才进入到.all()的逻辑里面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<h1>Promise</h1>
<button onclick="getData()">异步操作发请求</button>
</body>
<script type="text/javascript">
function p1(){
return new Promise(function(resolve,reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20)
if(num<=10){
resolve(num)
}else{
reject("again")
}
},2000)
})
}
function p2(){
return new Promise(function(resolve,reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20)
if(num<=10){
resolve(num)
}else{
reject("again")
}
},2000)
})
}
function p3(){
return new Promise(function(resolve,reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20)
if(num<=10){
resolve(num)
}else{
reject("again")
}
},2000)
})
}
function getData(){
Promise.all([p1(),p2(),p3()]).then(function(res){
console.log(res)
})
}
</script>
</html>
如果其中一个失败了,那么不会进入.all()逻辑,但是,实验得出,仍然能在.all()里打印错误信息????
function getData(){
Promise.all([p1(),p2(),p3()]).then(function(res){
console.log("三个promise都成功才会进入.all逻辑打印词条语句")
console.log(res)
},function(){
console.log("wrong")
})
}
而且在.then().catch()紧随的.catch()里依然能处理错误
function getData(){
Promise.all([p1(),p2(),p3()]).then(function(res){
console.log("三个promise都成功才会进入.all逻辑打印词条语句")
console.log(res)
}).catch(function(){
console.log("至少有一个Promise未成功")
})
}
??????关于Promise到此告一段落,以后可能补充。
Promise.race()顾名思义,race是竞争、比赛的意思,也是接收一个promise对象组成的数组作为参数所以先执行完的进入race逻辑,不论先执行完的是成功还是失败的,其余的都不再进行race逻辑,先执行完的不管是进行了race的成功回调还是失败回调,其余的将不会再进入race的任何回调,但还是会完成自身的逻辑,只是不进入race而已
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<h1>Promise.race</h1>
<button type="button" onclick="func1()">点击</button>
</body>
<script type="text/javascript">
function p1(){
return new Promise(function(resolve,reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20)
console.log("2s生成的数字:",num)
if(num<=10){
resolve(num)
}else{
reject("2s生成的数字大于10")
}
},2000)
})
}
function p2(){
return new Promise(function(resolve,reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20)
console.log("3s生成的数字:",num)
if(num<=10){
resolve(num)
}else{
reject("3s生成的数字大于10")
}
},3000)
})
}
function p3(){
return new Promise(function(resolve,reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20)
console.log("4s生成的数字:",num)
if(num<=10){
resolve(num)
}else{
reject("4s生成的数字大于10")
}
},4000)
})
}
function func1(){
Promise.race([p1(),p2(),p3()]).then(function(res){
console.log("只打印2s的结果,因为2s最先完成,它便进入该rall逻辑" + res)
},function(reason){
console.log(reason + "进入失败的回调")
})
}
</script>
</html>
- 防抖和节流防抖和节流看这个就够了
防抖:比如 一个onmousemove或者onscroll事件,鼠标移动或者滚动条滚动都是连续不断的,如果给这样的事件绑定一个方法,那么就会连续不断的执行里面的功能代码,这会带来性能问题,一个思路是,在事件发生时并不立即执行函数,而是在事件发生后的一段时间(比如1s)内没有再次触发,那么就执行这个函数。如果1s内再次触发,那么就重新计时,所以这里要用到延时器setTimeout()
原文:
基于上述场景,首先提出第一种思路:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后:
如果在200ms内没有再次触发滚动事件,那么就执行函数
如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时
效果:如果短时间内大量触发同一事件,只会执行一次函数。
function showTop(){
var top = document.documentElement.scrollTop
console.log(top)
}
function debounce(fn){
var timer
return function(){
if(timer){//需要注意的是 timer只有用clearTimeout()才能清除,也就是
//一个3s的延时器在3s后完成了里面函数执行后,这个timer依然在
console.log("timer")
clearTimeout(timer)
}
timer = setTimeout(fn,1000)
}
}
window.onscroll = debounce(showTop)
注意:timer只有在clearTimeout(timer)后才会消失,也就是一个3s的延时器在3s后完成了他的功能后,timer是依然存在的。
debounce()这个函数因为是闭包 ,所以无论滚动条怎么滚动它都仅仅执行了一次,反复执行的是里面的匿名函数体,这样就可以解释作者原代码中给timer,valid赋值的问题。闭包是为了避免全局污染,既然这样,那不用闭包应该也能实现节流和防抖。
**节流:**同样是短时间大量触发事件,但是有一个“技能冷却期”,即函数触发后一段时间内失效,过了这段事件又可以重新触发。类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)。实现 这里借助setTimeout来做一个简单的实现,加上一个状态位valid来表示当前函数是否处于工作状态:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
.box{
width: 100px;
height: 1500px;
background-color: dodgerblue;
}
</style>
</head>
<body>
<div class="box"></div>
</body>
<script type="text/javascript">
function throttle(fn,delay){
var valid = true
return function(){
if(!valid){
return
}
valid =false
setTimeout(()=>{
fn()
valid=true
},delay)
}
}
/* 请注意,节流函数并不止上面这种实现方案,
例如可以完全不借助setTimeout,可以把状态位换成时间戳,然后利用时间戳差值是否大于指定间隔时间来做判定。
也可以直接将setTimeout的返回的标记当做判断条件-判断当前定时器是否存在,如果存在表示还在冷却,并且在执行fn之后消除定时器表示激活,原理都一样
*/
// 以下照旧
function showTop () {
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
console.log('滚动条位置:' + scrollTop);
}
window.onscroll = throttle(showTop,1000)
</script>
</html>
- this
javascript中,this指的是它所属的对象
1.单独使用时,this指的是全局对象window
2.在方法中,指的是方法所属的对象
3.在函数中,指的是全局对象window
4.严格模式下,如果单独使用,指的是全局对象window,如果在函数中,指的是undefined。
5.在事件中,指的是接收事件的元素。
call()和apply()----person1.fullName.call(person2)用person1的fullName方法来处理person2的数据。
- 什么是盒模型
把每个元素都看成一个矩形框,包括4部分,边框、内容区、填充、外边距。 - 项目中的图片懒加载懒加载看这个就够了
什么是懒加载: 所谓的懒加载就是向下滚动的时候才会请求对应的图片。懒加载就是延迟加载的意思,比如一个图片资源很长,以及超出了浏览器的窗口,那么首先加载窗口能看见的部分,剩下的部分在向下滚动的时候再加载。
为什么要懒加载:
是一种网页性能优化的方式。极大提升用户体验,全部加载费时间费流量。
实现懒加载
核心思路是进入可视区域的图片才给他的src赋值。之前img标签并没有src,因为如果直接有了src那么网页加载则会加载所有图片,把真实的src写到自定义属性data-src里,后面当图片进入可视化窗口时再将着个 data-src里的真实src赋值给图片的src属性,那么如何判断图片是否进入可视化窗口呢?首先要知道三个api,1.h = document.documentElement.clientHeight—这是浏览器窗口的高度;2.s = document.documentElement.scrollTop这是滚动条的距离===浏览器可见窗口顶部到整个网页的顶部距离3.t = e.offsetTop这是当前这个元素到整个网页顶部的距离。当s + h>a时说明该图片进入了可视化区域,就该加载显示这张图片,给他赋值src.请看图
懒加载的代码便可以写出来了
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
img{
display: block;
}
</style>
</head>
<body>
<!-- 所有图片src属性最初是空的,否则就会全部加载,但是把src路径写到自定义属性data-src方面后期赋值 -->
<img data-src="D:\images\1d3347eff1e5e647bbef36920928631e.jpeg" >
<img data-src="D:\images\3e7d13cd434b7f35a18e548e06fdee10.jpeg" >
<img data-src="D:\images\04f7aac6012b75f7550e47dd1d48c088.jpeg" >
<img data-src="D:\images\7e948105afc34bac1be50f5f669cd8cd.jpeg" >
<img data-src="D:\images\45dd65a67a6afbae0019e760e5cfb139.jpeg" >
<img data-src="D:\images\143fdbd5fd31789425cf265c666331de.jpeg" >
<img data-src="D:\images\849b32ebd8e5c0622b0e5cad837605bc.jpeg" >
<img data-src="D:\images\1235adf40c99fdf2ed85134124237b98.jpeg" >
<img data-src="D:\images\7852d78e273023588306cbe21a988e6a.jpeg" >
<img data-src="D:\images\21268a74d8bc9cc2eea3f22466ebcc83.jpeg" >
<img data-src="D:\images\adec34178faa85652fa95bac175a77dd.jpeg" >
<img data-src="D:\images\dc9f152da66de392b39a95d00ee389da.jpeg" >
<img data-src="D:\images\f605882e54143866b43039bb4e3b598a.jpeg" >
</body>
<script type="text/javascript">
//获取所有的图片
var imgs = document.querySelectorAll("img")
//获取图片的offSetTop函数
function getTop(e) {
var t = e.offsetTop;
return t;
}
//懒加载函数
function lazyLoad(imgs){
//获取浏览器窗口的高
var h = document.documentElement.clientHeight
//获取滚动条的高度
var s = document.documentElement.scrollTop
for (var i = 0;i<imgs.length;i++){
//如果窗口高度 + 滚动条高度 > 图片到文档顶部的距离,说明图片进入可视区域,开始加载,给他src。
if(h + s > getTop(imgs[i])){
//添加src属性
imgs[i].src = imgs[i].getAttribute('data-src')
}
}
}
// 初始网页加载以及滚动时调用懒加载函数
window.onload = window.onscroll = function(){
lazyLoad(imgs)
}
</script>
</html>
- 数组去重
数组去重 1.利用ES6的Set方法
<script type="text/javascript">
var arr = [1,2,3,4,3,2,6]
var newarr = Array.from(new Set(arr))
console.log(newarr)
</script>
2.利用双重for + splice()
var arr = [1, 5, 6, 0, 7, 3, 0, 5, 9, 5, 5]
function unique(arr) {
for (var i = 0, iLen = arr.length; i < iLen; i++) {
for (var j = i + 1, jLen = arr.length; j < jLen; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1)
j-- // 每删除一个数j的值就减1
jLen-- // j值减小时len也要相应减1(减少循环次数,节省性能)
// console.log(j,jLen)
}
}
}
return arr
}
console.log(unique(arr)) // 1, 5, 6, 0, 7, 3, 9
3.利用indexOf(number)方法,arr.indexOf(number)–查找数组中number,如果找到返回第一次出现的下标,如果没找到返回-1;思路是先定义一个空的数组arr1,然后将那些在arr1.indexOf(i)中返回值为-1的插入到arr1
var arr = [1, 5, 6, 0, 7, 3, 0, 5, 9, 5, 5]
console.log(arr.indexOf(5))
var arr1 = []
for(var i = 0;i<arr.length;i++){
//arr.indexOF(number)在arr中找number,如果找到返回第一次出现的下标
//没找到返回-1
if(arr1.indexOf(arr[i])===-1){
arr1.push(arr[i])
}
}
4.利用arr.includes()找到返回true,否则false,等同于方法3
var arr = [1, 5, 6, 0, 7, 3, 0, 5, 9, 5, 5]
console.log(arr.indexOf(5))//1
var arr1 = []
for(var i = 0;i<arr.length;i++){
//arr.indexOF(number)在arr中找number,如果找到返回第一次出现的下标
//没找到返回-1
if(!arr1.includes(arr[i])){
arr1.push(arr[i])
}
}
- 普通函数和构造函数的区别
用function声明的都是函数,而如果直接调用的话,那么Person()就是一个普通函数,只有用函数new产生对象时,这个函数才是new出来对象的构造函数。
-
模块化和组件化的区别
组件化就是把重复的代码提取出来成为一个个组件,最重要的是完成复用,模块化就是把同一功能/业务的代码隔离成独立的模块,可以独立运行,独立管理,模块有接口可供调用。 -
前端兼容性
-
请求包含了哪些重要字段
-
移动端怎么适配
-
浏览器的兼容问题
兼容:其实就是同一个网页在不同浏览器下的显示效果不一致,原因是1.css和html在不断发展,进步缓慢的IE浏览器跟不上这个节奏,所以就会出现很多不能识别的东西,2.再就是虽然识别了.但定义的范围或者一些默认的参数会不同,因为浏览器内核差异,最值得吐槽的是IE浏览器和别的很多浏览器都不一样,这样也不会兼容. -
浏览器缓存
-
重要的状态码
-
怎么兼容适配移动端
瀑布流–戳这里
思路:这种布局的图片一定是宽度都相同,高度不同,首先定义好图片的宽度width,然后通过浏览器宽度除以这个宽度得到一行排要排列的图片num,然后把这些图片的高度放进一个数组。找到这个数组中高度最小的值,那么接下来的一张图片就插入这个地方,这张图片的CSStop就是高度最小图片的高度,left就是width*index,把这张图片重新放入数组,再次重新比较,就可以一直重复,
登录验证–导航守卫 token-- 戳这里
// 2. 创建router实例
const router = new VueRouter({
routes,
})
// 导航守卫
// to: 将要访问的路径
// from: 代表从哪个路径跳转而来
// next: 是一个函数, 代表放行
// next(): 放行 next('/home'): 强制跳转
router.beforeEach((to, from, next) => {
// // 如果将要访问登录页直接放行
// if (to.path === '/login') return next()
// // 如果token没有值,强制跳转到login
// const tokenStr = window.sessionStorage.getItem('token')
// if (!tokenStr) return next('/login')
// next()
if (to.path === '/login') {
next()
} else {
const tokenStr = window.sessionStorage.getItem('token')
if (!tokenStr) {
next('/login')
} else {
next()
}
}
})
// 3. 导出
export default router
es6中拼接字符串有几种写法?
vue中什么是递归组件?戳这里
比如一个地名 四川省 下面成都市 资阳市 成都市 下面武侯区 新都区 这种树状结构,使用递归组件实现展示,关键是循环的变量,起初是Treedata 里面递归的循环的是treedata的children
<!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>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="app"></div>
<script>
Vue.component('Tree', {
// 【注意 1】要做递归,要加上name属性;
// 全局组件可以不要,因为会自动生成,局部组件一定要添加上 name
name: 'Tree',
props: {
treeData: Array
},
template: `
<ul>
<!--【注意 2】在循环的 li里边,调用了自身组件-->
<li v-for="item in treeData">
<p @click="item.show = !item.show">{{item.name}}</p>
<Tree :treeData="item.children"
v-if="Array.isArray(item.children)"
v-show="item.show"/>
</li>
</ul>
`,
});
new Vue({
el: "#app",
template: `
<Tree :treeData="treeData"/>
`,
data: {
treeData:
[
{
name: '山东省',
show: true,
children: [
{
name: '潍坊市',
show: true,
children: [
{
name: '临朐县',
show: true,
children:[
{
name:'中天镇',
show:true,
}
]
},
{
name: '潍城区',
show: true,
},
{
name: '奎文区',
show: true,
},
{
name: '寿光县',
show: true,
}
]
},
{
name: '日照市',
show: true,
children: [
{
name: '岚山区',
show: true,
},
{
name: '五莲县',
show: true,
}
]
},
{
name: '济南市',
show: true,
children: [
{
name: '市中区',
show: true,
},
{
name: '高新区',
show: true,
},
{
name: '章区',
show: true,
},
{
name: '槐荫区',
show: true,
}
]
},
]
},
{
name: '陕西省',
show: true,
children: [
{
name: '西安市',
show: true,
children: [
{
name: '未央区',
show: true,
},
{
name: '莲湖区',
show: true,
children: [
{
name: '红庙坡街道',
show: true,
},
{
name: '梨园路',
show: true,
}
]
},
{
name: '高新区',
show: true,
},
{
name: '浐灞',
show: true,
}
]
},
{
name: '咸阳市',
show: true,
},
{
name: '汉中市',
show: true,
}
]
}
]
},
})
</script>
</body>
</html>
小程序页面间有哪些传递数据的方法
小程序的双向绑定和vue哪里不一样
阐述Object.assign的用法,深拷贝与浅拷贝的区别?
对象的合并,,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
微信小程序中rpx与px的比例是多少?em与rem分别代表什么意思?
项目里遇到的难点?–戳这里
什么是mvvm,mvc?区别?移步
这里
去除字符串空格
1.正则
str.replace(/\s*/g,'')
2.trim–不能去除中间的空格
去两边str.trim()
去左边str.trimLeft()
去右边str.trimRight()
3.jquery去除
var str = " xiao ming ";
var str2 = $.trim(str)
3、js 字符串操作函数
concat()– 将两个或多个字符的文本组合起来,返回一个新的字符串。
indexOf()– 返回字符串中一个子串第一处出现的索引。如果没有匹配项,返回-1。
charAt()– 返回指定位置的字符。
lastIndexOf()– 返回字符串中一个子串最后一处出现的索引,如果没有匹配项,返回-1。
match()– 检查一个字符串是否匹配一个正则表达式。
substr()函数–返回从string的startPos位置,长度为length的字符串
substring()– 返回字符串的一个子串。传入参数是起始位置和结束位置。
slice()– 提取字符串的一部分,并返回一个新字符串。
replace()– 用来查找匹配一个正则表达式的字符串,然后使用新字符串代替匹配的字符串。
search()– 执行一个正则表达式匹配查找。如果查找成功,返回字符串中匹配的索引值。否则返回-1。
split()– 通过将字符串划分成子串,将一个字符串做成一个字符串数组。
length– 返回字符串的长度,所谓字符串的长度是指其包含的字符的个数。
toLowerCase()– 将整个字符串转成小写字母。
toUpperCase()– 将整个字符串转成大写字母。
6、比较typeof与instanceof?
相同点:JavaScript中typeof和instanceof常用来判断一个变量是否为空,或者是什么类型的。
typeof的定义和用法:返回值是一个字符串,用来说明变量的数据类型。
细节:
1)、typeof一般只能返回如下几个结果:
number,boolean,string,function,object,undefined。
2)、typeof来获取一个变量是否存在,如if(typeof a!=“undefined”){alert(“ok”)},而不要去使用if(a)因为如果a不存在(未声明)则会出错。
3)、对于Array,Null等特殊对象使用typeof一律返回object,这正是typeof的局限性。
Instanceof定义和用法:instanceof用于判断一个变量是否属于某个对象的实例。
实例演示:
a instanceof b?alert(“true”):alert(“false”); //a是b的实例?真:假
var a = new Array();
alert(a instanceof Array); // true
alert(a instanceof Object) // true
如上,会返回true,同时alert(a instanceof Object)也会返回true;这是因为Array是object 的子类。
function test(){};
var a = new test();
alert(a instanceof test) // true
细节:
(1)、如下,得到的结果为‘N’,这里的instanceof测试的object是指js语法中的object,不是指dom模型对象。
if (window instanceof Object){ alert(‘Y’)} else { alert(‘N’);} // ‘N’
vue插槽
这个就够了–那我要开多个按钮呢?是不是留几个插槽??
更易懂
在构建页面的过程中一般把用的比较多的公共的部分抽取出来作为一个组件,但是实际使用过程中又不能完全的满足要求,有的需要一些其他的功能,这个时候就可以利用插槽来分发内容,实现组件的扩展。
这是父组件
<template>
<div>
<div>大家好我是父组件</div>
<myslot>---引用进来的,这是子组件
<p>测试一下吧内容写在这里了能否显示</p>
</myslot>
</div>
</template>
<script>
import myslot from './myslot';
export default {
components: {
myslot
}
}
</script>
<style>
</style>
子组件
<template>
<div>
<div>我是子组件</div>
</div>
</template>
<script>
</script>
<style>
</style>
结果是
可以看到子组件<myslot>
里的内容并没有渲染出来,也就是不用插槽,子组件中间写的东西都会被忽略。
如果要展示就用插槽
<template>
<div>
<div>我是子组件</div>
<p>现在测试一下slot</p>
<slot></slot>
</div>
</template>
<script>
</script>
<style>
</style>
修改子组件,添加一个<slot>
结果
**插槽的作用:**让用户可以扩展组件,去更好地复用组件和做定制化处理。
插槽的分类
默认插槽(匿名插槽)
可以在插槽标签写内容<slot>默认</slot>
如果要改变的话 就在父组件使用这个子组件的时候,把想写的写进去
具名插槽
有时候子组件可能有多个插槽,那么怎么保证他们的对应关系,这就是具名插槽。
同样子组件开插槽
<!-- 插槽名称为:heading -->
<slot name="heading"></slot>
<!-- 插槽名称为:sub-heading -->
<slot name="sub-heading"></slot>
通过name属性
父组件中
<child>
<template v-slot:heading>
<h1>element-ui组件</h1>
</template>
<template v-slot:sub-heading>
<p>这里是element-ui的部分组件介绍</p>
</template>
<template v-slot:footer-text>
<p>出品@小土豆</p>
</template>
</child>
一般是写在<template>
这样不会渲染真实的DOM,可以写在<div>
,但是会渲染多余的div
作用域插槽
简单说就是父组件访问子组件的数据,或者说子组件传值给父组件。
子组件通过v-bind
把要传递的数据绑定
<!-- 子组件: /slot-demo/src/components/Child.vue -->
<template>
<div class="child">
<slot
name="heading"
v-bind:headingValue="heading">
{{heading}}
</slot>
<!-- 为了让大家看的更清楚 已经将Child.vue组件中多余的内容删除 -->
</div>
</template>
<script>
export default {
name: 'Child',
data() {
return {
heading: '这里是默认的heading'
}
}
}
</script>
父组件接收:接着我们就可以在父组件中定义一个变量来接收子组件中传递的数据。
父组件中接收数据的变量名可以随意起,这里我起的变量名为slotValue
<template>
<div id="app">
<child>
<template v-slot:heading="slotValue" >
<h1>element-ui组件</h1>
slotValue = {{slotValue}}
</template>
</child>
</div>
</template>
结果是
接收到的是一个对象。
应用场景
假设有一个card组件(作为APP组件的子组件),
<template>
<div class="card">
<h3>{{title}}</h3>
<p v-for="item in list" :key="item.id">
{{item.id}}.{{item.text}}
</p>
</div>
</template>
<script>
export default {
name: 'Card',
props: ['title', 'list'],
data() {
return {
}
}
}
</script>
其中Card组件中展示的title和list数据由父组件传入。接着在App组件中复用Card组件,并且传入title和list数据。
<template>
<div id="app">
<card :list="list" :title="title">
</card>
</div>
</template>
<script>
import Card from './components/Card.vue'
export default {
name: 'App',
components: {
Card,
},
data() {
return {
title: '名人名言',
list:[
{
id:1,
text:'要成功,先发疯,头脑简单向前冲'
},{
id:2,
text:'不能天生丽质就只能天生励志!'
},{
id:3,
text:'世上唯一不能复制的是时间,唯一不能重演的是人生。'
}
]
}
}
}
</script>
结果
如果要在title名人名言前加一个字体图标和不显示序号,首先想到是插槽做组件的扩展而不是重写组件,方法是给Card子组件添加插槽
<!-- Card组件:/slot-demo/src/components/Card.vue -->
<template>
<div class="card">
<h3>
<slot name="title" v-bind:titleValue="title"> {{title}} </slot>
</h3>
<p v-for="item in list" :key="item.id">
<slot name="text" v-bind:itemValue="item">{{item.id}}.{{item.text}}</slot>
</p>
</div>
</template>
父组件
<!-- App组件:/slot-demo/src/App.vue -->
<card :list="list" :title="title">
<template v-slot:title="slotTitle">
<i class="el-icon-guide"></i>{{slotTitle.titleValue}}
</template>
</card>
效果
页面不显示序号
<!-- App组件:/slot-demo/src/App.vue -->
<card :list="list" :title="title">
<template v-slot:text="slotItem">
{{slotItem.itemValue.text}}
</template>
</card>
v-slot:heading="slotValue"
父组件几指明了插槽又绑定了数据
vue 路由模式
拦截器
性能优化
vue react 组件通信
VUe组件通信(传值)vue通信
1.父组件向子组件:通过props;父组件里的子组件标签 里通过v-bind动态绑定父组件data里的数据,然后子组件在自己的props里声明接收。
2.子组件向父组件:子组件通过 this.
e
m
i
t
(
"
t
i
t
l
e
C
h
a
n
g
e
d
"
,
"
子
向
父
组
件
传
值
"
)
;
发
送
一
个
事
件
名
和
要
传
的
值
,
父
组
件
通
过
v
−
o
n
:
t
i
t
l
e
C
h
a
n
g
e
d
=
"
u
p
d
a
t
e
T
i
t
l
e
"
;
来
监
听
(
接
收
)
这
个
事
件
,
然
后
在
新
的
u
p
d
a
t
e
T
i
t
l
e
事
件
里
来
处
理
这
个
值
。
3.
通
过
中
央
事
件
总
线
,
实
现
任
何
组
件
的
通
信
,
包
括
父
子
,
兄
弟
,
跨
级
。
具
体
是
通
过
一
个
空
的
v
u
e
实
例
e
v
e
n
t
b
u
s
,
实
现
发
送
数
据
方
,
e
v
e
n
t
b
u
s
.
‘
emit("titleChanged","子向父组件传值");发送一个事件名和要传的值,父组件通过v-on:titleChanged="updateTitle";来监听(接收)这个事件,然后在新的updateTitle事件里来处理这个值。 3.通过中央事件总线,实现任何组件的通信,包括父子,兄弟,跨级。具体是通过一个空的vue实例eventbus,实现发送数据方,eventbus.`
emit("titleChanged","子向父组件传值");发送一个事件名和要传的值,父组件通过v−on:titleChanged="updateTitle";来监听(接收)这个事件,然后在新的updateTitle事件里来处理这个值。3.通过中央事件总线,实现任何组件的通信,包括父子,兄弟,跨级。具体是通过一个空的vue实例eventbus,实现发送数据方,eventbus.‘emit (事件名,数据) 接收方eventbus.
$on`(事件名,callback)来监听
4.vuex
React组件通信(传值)
1.父组件向子组件传值,通过props,父组件在子组件的标签体里<Child message={{this.state.message}}></Child>
(类似vue,只是不是动态绑定,关键是react也没有v-bind,所以也只能这么写);子组件通过props接收使用;<div>{this.props.message}</div>
2.子组件向父组件传值,本质上也是props,父组件先定义一个函数,通过子组件通过props接收,并在适当时候触发一个事件,在这个事件里通过this.props.handle
来触发这个函数
父组件
class Parent extends Component{
this.state = {
message: 'hello'
};
transferMsg(msg){
this.setState({
message
});
}
render(){
return <div>
<p>child msg: {this.state.message}</p>
<Child transferMsg = {msg => this.transferMsg(msg)} />
</div>
}
}
子组件
class Child extends Component{
componentDidMount(){
setTimeOut( () => {
this.props.transferMsg('world')
},2000)
}
render(){
return <div>child</div>
}
}
3.引入一个模块 发送方pubsub.publish;接收方pubsub.subscribe
4.redux
微信小程序组件通信(传值)
uniapp组件通信(传值)
vuex
redux
vue 声明周期及其应用场景
react声明周期及其应用场景 废除的(即将)新加的
vuex缺点(可以这样说吗?)
vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。
vue双向绑定数组不行?why?
性能优化(路由懒加载、组件懒加载等)
兼容性
遇到最大的问题,是怎么解决的?
在浏览器输入地址,发生了哪些事?
http协议
高阶组件
高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件,而高阶组件是将组件转换为另一个组件