1、移动端1px问题,如何解决?
原因:由于不同的手机有不同的像素密度导致的。如果移动显示屏的分辨率始终是普通屏幕的2倍,1px的边框在devicePixelRatio=2的移动显示屏下会显示成2px,所以在高清屏下看着1px总是感觉变胖了
解决方法:
一.在ios8+中当devicePixelRatio=2的时候使用0.5px
p{
border:1px solid #000;
}
@media (-webkit-min-device-pixel-ratio: 2) {
p{
border:0.5px solid #000;
}
}
二.伪元素 + transform 实现
对于老项目伪元素+transform是比较完美的方法了。
原理是把原先元素的 border 去掉,然后利用 :before 或者 :after 重做 border ,并 transform 的 scale 缩小一半,原先的元素相对定位,新做的 border 绝对定位。
单条border样式设置:
.scale-1px{
position: relative; border:none; }
.scale-1px:after{
content: '';
position: absolute; bottom: 0;
background: #000;
width: 100%; height: 1px;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
优点:所有场景都能满足,支持圆角(伪元素和本体都需要加border-radius)
缺点:对于已经使用伪元素的元素(例如clearfix),可能需要多层嵌套
三.viewport + rem 实现
这种兼容方案相对比较完美,适合新的项目,老的项目修改成本过大。
在devicePixelRatio = 2 时,输出viewport:
在devicePixelRatio = 3 时,输出viewport:
优点:所有场景都能满足,一套代码,可以兼容基本所有布局
缺点:老项目修改代价过大,只适用于新项目
四,使用box-shadow模拟边框
利用css 对阴影处理的方式实现0.5px的效果
样式设置:
.box-shadow-1px {
box-shadow: inset 0px -1px 1px -1px #c8c7cc;
}
优点:代码量少,可以满足所有场景
缺点:边框有阴影,颜色变浅
2、伪类和伪对象的区别
伪类和伪元素(伪对象)的根本区别在于:它们是否创造了新的元素
伪元素/伪对象:不存在在DOM文档中,是虚拟的元素,是创建新元素。代表某个元素的子元素,这个子元素虽然在逻辑上存在,但却并不实际存在于文档树中。
伪类:存在DOM文档中,逻辑上存在但在文档树中却无须标识的“幽灵”分类。
3、清除浮动的几种方式
额外标签法:给谁清除浮动,就在其后额外添加一个空白标签 。
父级添加overflow方法
使用after伪元素清除浮动
4、移动端适配方案
rem+vw布局
vw表示1%的屏幕宽度,而我们的设计稿通常是750px的,屏幕一共是100vw,对应750px,那么1px就是0.1333333vw, 为了方便计算,我们放大100倍,同时我们知道另一个单位rem,rem是相对html元素,为了方便计算,我们取html是100px,通过上面的计算结果1px是0.13333333vw,那么100px就是13.333333vw了.这样后面的用rem就很好计算了,这样就得到13.3333333vw对应100px(750px的 设计稿),然后我们就可以很愉快的写rem单位了
5、标准盒模型和怪异盒模型区别
标准盒与怪异盒的区别在于他们的总宽度的计算公式不一样。标准模式下总宽度=width+margin(左右)+padding(左右)border(左右);
怪异模式下总宽度=width+margin(左右)(就是说width已经包含了padding和border值)
6、回流和重绘
当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候,这时候是一定会发生回流的,因为要构建render tree。
当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。
他们的区别很大:
回流必将引起重绘,而重绘不一定会引起回流。比如:只有颜色改变的时候就只会发生重绘而不会引起回流
当页面布局和几何属性改变时就需要回流
7、遇到的浏览器兼容问题,如何解决
*
浏览器兼容问题一:不同浏览器的标签默认的外补丁和内补丁不同
问题症状:随便写几个标签,不加样式控制的情况下,各自的margin 和padding差异较大。
碰到频率:100%
解决方案:CSS里 {margin:0;padding:0;}
备注:这个是最常见的也是最易解决的一个浏览器兼容性问题,几乎所有的CSS文件开头都会用通配符来设置各个标签的内外补丁是0。
*
浏览器兼容问题二:块属性标签float后,又有横行的margin情况下,在IE6显示 margin比设置的大
问题症状:常见症状是IE6中后面的一块被顶到下一行
碰到频率:90%(稍微复杂点的页面都会碰到,float布局最常见的浏览器兼容问题)
解决方案:在float的标签样式控制中加入 display:inline;将其转化为行内属性
备注:我们最常用的就是div+CSS布局了,而div就是一个典型的块属性标签,横向布局的时候我们通常都是用div float实现的,横向的间距设置如果用margin实现,这就是一个必然会碰到的兼容性问题。
*
浏览器兼容问题三:设置较小高度标签(一般小于10px),在IE6,IE7,遨游中高度超出自己设置高度
问题症状:IE6、7和遨游里这个标签的高度不受控制,超出自己设置的高度
碰到频率:60%
解决方案:给超出高度的标签设置overflow:hidden;或者设置行高line-height 小于你设置的高度。
备注:这种情况一般出现在我们设置小圆角背景的标签里。出现这个问题的原因是IE8之前的浏览器都会给标签一个最小默认的行高的高度。即使你的标签是空的,这个标签的高度还是会达到默认的行高。
*
浏览器兼容问题四:行内属性标签,设置display:block后采用float布局,又有横行的margin的情况,IE6间距bug
问题症状:IE6里的间距比超过设置的间距
碰到几率:20%
解决方案:在display:block;后面加入display:inline;display:table;
备注:行内属性标签,为了设置宽高,我们需要设置display:block;(除了input标签比较特殊)。在用float布局并有横向的margin后,在IE6下,他就具有了块属性float后的横向margin的bug。不过因为它本身就是行内属性标签,所以我们再加上display:inline的话,它的高宽就不可设了。这时候我们还需要在display:inline后面加入display:talbe。
*
浏览器兼容问题五:图片默认有间距
问题症状:几个img标签放在一起的时候,有些浏览器会有默认的间距,加了问题一中提到的通配符也不起作用。
碰到几率:20%
解决方案:使用float属性为img布局
备注:因为img标签是行内属性标签,所以只要不超出容器宽度,img标签都会排在一行里,但是部分浏览器的img标签之间会有个间距。去掉这个间距使用float是正道。(我的一个学生使用负margin,虽然能解决,但负margin本身就是容易引起浏览器兼容问题的用法,所以我禁止他们使用)
*
浏览器兼容问题六:标签最低高度设置min-height不兼容
问题症状:因为min-height本身就是一个不兼容的CSS属性,所以设置min-height时不能很好的被各个浏览器兼容
碰到几率:5%
解决方案:如果我们要设置一个标签的最小高度200px,需要进行的设置为:{min-height:200px; height:auto !important; height:200px; overflow:visible;}
备注:在B/S系统前端开时,有很多情况下我们又这种需求。当内容小于一个值(如300px)时。容器的高度为300px;当内容高度大于这个值时,容器高度被撑高,而不是出现滚动条。这时候我们就会面临这个兼容性问题。
8、盒子垂直水平居中方案
1、内容块定义transform: translate(-50%,-50%) 必须加上
top: 50%; left: 50%; position: absolute;
2、弹性盒
3、定位
9、@import和link区别
加载顺序
link引入的css在加载页面时同时加载,而@import中的css要在页面加载完毕后加载
从属关系
link是HTML提供的标签
@import是css的语法规则,只能加载在style标签内和css文件中
兼容性
@import是css2.1时提出的,只能用于IE5+,而link不受兼容影响
DOM可控性
link支持js控制DOM改变样式,而@import不支持
10、css选择器
1、标签选择器
2、ID选择器:规定用#来定义(名字自定义)
3、类选择器:规定用圆点.来定义
4、通配符*:匹配任何标签
5、后代选择器:用空格隔开
6、交集选择器:用.隔开
7、并集选择器(分组选择器):用逗号隔开
8、伪类选择器
1、原生js的dom操作
https://www.cnblogs.com/wfblog/p/8862946.html
2、let、const、var区别
一,var定义的变量,作用域是整个封闭函数,是全域的;let定义的变量,作用域是在块级或者字块中;
二,变量提升:不论通过var声明的变量处于当前作用于的第几行,都会提升到作用域的最顶部。
而let声明的变量不会在顶部初始化,凡是在let声明之前使用该变量都会报错(引用错误ReferenceError);
三,只要块级作用域内存在let,它所声明的变量就会绑定在这个区域;
四,let不允许在相同作用域内重复声明(报错同时使用var和let,两个let)。
const用来专门声明一个常量,它跟let一样作用于块级作用域,没有变量提升,重复声明会报错,不同的是const声明的常量不可改变,声明时必须初始化(赋值)
3、箭头函数和普通函数区别
一.外形不同:
箭头函数使用箭头定义,普通函数中没有。
二.箭头函数全都是匿名函数:
普通函数可以有匿名函数,也可以有具名函数。
三.箭头函数不能用于构造函数:
普通函数可以用于构造函数,以此创建对象实例。
四.箭头函数中this的指向不同:
在普通函数中,this总是指向调用它的对象或者,如果用作构造函数,它指向创建的对象实例。
五.箭头函数不具有arguments对象:
每一个普通函数调用后都具有一个arguments对象,用来存储实际传递的参数。
但是箭头函数并没有此对象。
4、new操作符做了什么
(1) 创建一个新对象;
(2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象) ;
(3) 执行构造函数中的代码(为这个新对象添加属性) ;
(4) 返回新对象。
function A(){};
var obj = new A();中一共实现了三步操作:
创建一个空对象obj var obj= {}
将构造函数的prototype赋给对象的__proto__obj.proto = A.prototype
构造函数对象的this指针指向objA.call(obj)
5、什么是回调函数,举例说明
回调函数就是一个参数,将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数。这个过程就叫做回调。
//定义主函数,回调函数作为参数
function A(callback) {
callback();
console.log('我是主函数');
}
//定义回调函数
function B(){
setTimeout("console.log('我是回调函数')", 3000);//模仿耗时操作
}
//调用主函数,将函数B传进去
A(B);
//输出结果
我是主函数
我是回调函数
6、null和undefined区别
(1)null是一个表示”无”的对象,转我数值是为0,undefined是一个表示”无”的原始值,转为数值时为NaN。当声明的变量还未被初始化时,能量的默认值为undefined
(2)Null用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象
(3)Undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义。典型用法是:
a、变量被声明了,但没有赋值时,就等于undefined
b、调用函数时,应该提供的参数没有提供,该参数等于undefined
c、对象没有赋值属性,该属性的值为undefined
d、函数没有返回值时,默认返回undefined
(4)null表示”没有对象”,即该处不应该有值。典型用法是:
a、作为函数的参数,表示该函数的参数不是对象
b、作为对象原型链的终点
7、this关键字原理
this总是指向调用它所在方法的对象。因为this是在函数运行时,自动生成的一个内部对象,只能在函数内部使用。
8、原型链和继承
https://www.jianshu.com/p/08c07a953fa0
https://www.cnblogs.com/ranyonsue/p/11201730.html
一、 什么是原型链?
每个对象都可以有一个原型_proto_,这个原型还可以有它自己的原型,以此类推,形成一个原型链。查找特定属性的时候,我们先去这个对象里去找,如果没有的话就去它的原型对象里面去,如果还是没有的话再去向原型对象的原型对象里去寻找… 这个操作被委托在整个原型链上,这个就是我们说的原型链了。
二、原型指针
我们知道了原型的概念,接下来我们就照着上面的图来具体分析一下原型的指针;中间最上面蓝色模块标注的构造函数Foo, 里面有两个属性: proto 和 prototype, 这两个很容易使人混淆,先说说prototype:
prototype:
prototype属性,它是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象; 这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象);
proto:
proto 是原型链查询中实际用到的,它总是指向 prototype,换句话说就是指向构造函数的原型对象,它是对象独有的。注意,为什么Foo构造也有这个属性呢,因为再js的宇宙里万物皆对象,包括函数;
根据以上的概括我们能知道Foo构造函数_proto_指向的是他的构造函数的原型对象,它的构造函数是Function, 也就是说Foo的_proto_指向Function.prototype, 我们再看到左边绿色的a和b函数的_proto_指像的是Foo.prototype,因为他们是通过 new Foo实例化出来的,它们的构造函数就是Foo(), 即a.proto = Foo.prototype; 接着我们来看看最右边紫色的模块Function.prororype, 它的_proto_指针指向的是Object.prototype,Object._proto_又为null.。于是我们就可以得出:在原型链中的指向是,函数 → 构造行数 → Function.prototype → Object.protype → null ;
constructor:
我们看到途中最中间灰色模块有一个constructor属性,这个又是做什么用的呢?
每个函数都有一个原型对象,该原型对象有一个constructor属性,指向创建对象的函数本身。
此外,我们还可以使用constructor属性,所有的实例对象都可以访问constructor属性,constructor属性是创建实例对象的函数的引用。我们可以使用constructor属性验证实例的原型类型(与操作符instanceof非常类似)。
由于constructor属性仅仅是原始构造函数的引用,因此我们可以使用该属性创建新的对象,如:
通过第一个对象实例化对象的constuctor方法创建第2个实例化对象,说明创建的新对象ninja2 是Ninja的实例,由于ninja和ninja2不是同一个对象可以得出它们是两个截然不同的实例;
结论:
1、proto 是原型链查询中实际用到的,它总是指向 prototype;
2、prototype 是函数所独有的,在定义构造函数时自动创建,它总是被 proto 所指。
所有对象都有__proto__属性,函数这个特殊对象除了具有__proto__属性,还有特有的原型属性prototype。prototype对象默认有两个属性,constructor属性和__proto__属性。prototype属性可以给函数和对象添加可共享(继承)的方法、属性,而__proto__是查找某函数或对象的原型链方式。constructor,这个属性包含了一个指针,指回原构造函数。
继承:构造函数继承、原型继承、原型链继承、es6 class继承
9、try catch用法
try/catch/finally 语句用于处理代码中可能出现的错误信息。
错误可能是语法错误,通常是程序员造成的编码错误或错别字。也 可能是拼写错误或语言中缺少的功能(可能由于浏览器差异)。
try语句允许我们定义在执行时进行错误测试的代码块。
catch 语句允许我们定义当 try 代码块发生错误时,所执行的代码块。
finally 语句在 try 和 catch 之后无论有无异常都会执行。
注意: catch 和 finally 语句都是可选的,但你在使用 try 语句时必须至少使用一个。
提示: 当错误发生时, JavaScript 会停止执行,并生成一个错误信息。使用 throw 语句 来创建自定义消息(抛出异常)。如果你将 throw 和 try 、 catch一起使用,就可以控制程序输出的错误信息。
10、sessionStorage、localStorage、cookie区别
1)存储大小
cookie:一般不超过4K(因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识)
sessionStorage:5M或者更大
localStorage:5M或者更大
2)数据有效期
cookie:一般由服务器生成,可以设置失效时间;若没有设置时间,关闭浏览器cookie失效,若设置了时间,cookie就会存放在硬盘里,过期才失效
sessionStorage:仅在当前浏览器窗口关闭之前有效,关闭页面或者浏览器会被清除
localStorage:永久有效,窗口或者浏览器关闭也会一直保存,除非手动永久清除,因此用作持久数据
3)作用域
cookie:在所有同源窗口中都是共享的
sessionStorage:在同一个浏览器窗口是共享的(不同浏览器、同一个页面也是不共享的)
localStorage:在所有同源窗口中都是共享的
4)通信
ccokie:十种携带在同源的http请求中,即使不需要,故cookie在浏览器和服务器之间来回传递;如果使用cookie保存过多数据会造成性能问题
sessionStorage:仅在客户端(即浏览器)中保存,不参与和服务器的通信;不会自动把数据发送给服务器,仅在本地保存
localStorage:仅在客户端(即浏览器)中保存,不参与和服务器的通信;不会自动把数据发送给服务器,仅在本地保存
5)易用性
cookie:需要自己进行封装,原生的cookie接口不够友好
sessionStorage:原生接口可以接受,可以封装来对Object和Array有更好的支持
localStorage:原生接口可以接受,可以封装来对Object和Array有更好的支持
11、promise原理,promise封装ajax
promise是什么?
1、主要用于异步计算
2、可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
3、可以在对象之间传递和操作promise,帮助我们处理队列
promise详解
new Promise(
function (resolve, reject) {
// 一段耗时的异步操作
resolve('成功') // 数据处理完成
// reject('失败') // 数据处理出错
}).then(
(res) => {
console.log(res)}, // 成功
(err) => {
console.log(err)} // 失败)
promise封装ajax:
function fetch(method, url, data){
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
var method = method || "GET";
var data = data || null;
xhr.open(method, url, true);
xhr.onreadystatechange = function() {
if(xhr.status === 200 && xhr.readyState === 4){
resolve(xhr.responseText);
} else {
reject(xhr.responseText);
}
}
xhr.send(data);
})
}
// 使用
fetch("GET", "/some/url.json", null)
.then(result => {
console.log(result);
})
12、for in和for of区别
for in遍历数组的毛病
1.index索引为字符串型数字,不能直接进行几何运算
2.遍历顺序有可能不是按照实际数组的内部顺序
3.使用for in会遍历数组所有的可枚举属性,包括原型。例如上栗的原型方法method和name属性
所以for in更适合遍历对象,不要使用for in遍历数组。
13、'use strict’作用?
ES6的模块自动采用严格模式,不管你有没有在模块头部加上"use strict"
严格模式主要有以下限制:
变量必须声明后再使用
函数的参数不能有同名属性,否则报错
不能使用with语句
不能对只读属性赋值,否则报错
不能使用前缀0表示八进制数,否则报错
不能删除不可删除的属性,否则报错
不能删除变量delete prop ,会报错,只能删除属性 selete global[prop]
eval 不会在它的外层作用域引入变量
eval和arguments不能被重新赋值
arguments不会自动反映函数参数的变化
不能使用arguments.callee
不能使用arguments.caller
禁止this指向全局对象
不能使用fn.caller和fn.arguments获取函数调用的堆栈
增加了保留字(比如protected、static和interface)
14、bind、apply、call区别
apply方法
apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入,且当第一个参数为null、undefined的时候,默认指向window(在浏览器中),使用apply方法改变this指向后原函数会立即执行,且此方法只是临时改变thi指向一次。
日常用法:改变this指向
示例:
回调函数绑定this指向:
var name="martin";
var obj={
name:"lucy",
say:function(year,place){
console.log(this.name+" is "+year+" born from "+place);
}
};
var say=obj.say;
setTimeout(function(){
say.apply(obj,["1996","China"])
} ,0); //lucy is 1996 born from China,this改变指向了obj
say("1996","China") //martin is 1996 born from China,this指向window,说明apply只是临时改变一次this指向
小技巧:改变参数传入方式
示例:
求数组中的最大值:
var arr=[1,10,5,8,3];
console.log(Math.max.apply(null, arr)); //10
其中Math.max函数的参数是以参数列表,如:Math.max(1,10,5,8,3)的形式传入的,因此我们没法直接把数组当做参数,但是apply方法可以将数组参数转换成列表参数传入,从而直接求数组的最大值。
call方法
call方法的第一个参数也是this的指向,后面传入的是一个参数列表(注意和apply传参的区别)。当一个参数为null或undefined的时候,表示指向window(在浏览器中),和apply一样,call也只是临时改变一次this指向,并立即执行。
示例:
var arr=[1,10,5,8,3];
console.log(Math.max.call(null,arr[0],arr[1],arr[2],arr[3],arr[4])); //10
采纳以参数列表的形式传入,而apply以参数数组的形式传入。
bind方法
bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。
示例:
var arr=[1,10,5,8,12];
var max=Math.max.bind(null,arr[0],arr[1],arr[2],arr[3])
console.log(max(arr[4])); //12,分两次传参
可以看出,bind方法可以分多次传参,最后函数运行时会把所有参数连接起来一起放入函数运行。
实现bind方法(面试题):
简易版
Function.prototype.bind=function () {
var _this=this;
var context=arguments[0];
var arg=[].slice.call(arguments,1);
return function(){
arg=[].concat.apply(arg,arguments);
_this.apply(context,arg);
}
};
15、事件冒泡原理及如何阻止它
事件冒泡就是在事件开始时,由最具体的元素(文档中最底层的那个节点)接受,然后逐级向上传播到最不具体的节点(文档)。比如说,在一个页面有一个div元素,加上一个点击事件,在点击事件触发的时候,它的传递过程就是由div→body→html→document(老版本暂且不谈),它会沿着DOM树逐级的传递。
if(event.storpPrapagation){
event.storpPrapagation();
}else{
cancelBubble=true;
}
16、事件监听和事件委托
https://blog.csdn.net/HimBer/article/details/82459588
17、跨域问题怎么解决
https://segmentfault.com/a/1190000011145364
18、前端性能优化方案
https://www.cnblogs.com/xiaohuochai/p/9178390.html
19、js中如何检测一个变量是一个String类型?代码实现
方法1、
function isString(obj){
return typeof(obj) === "string"? true: false;
// returntypeof obj === "string"? true: false;
}
方法2、
function isString(obj){
return obj.constructor === String? true: false;
}
方法3、
function isString(obj){
return Object.prototype.toString.call(obj) === "[object String]" ? true : false;
}
如:
var isstring = isString('xiaoming');
20、如何比较两个对象
① 方法一:通过JSON.stringify(obj)来判断两个对象转后的字符串是否相等
优点:用法简单,对于顺序相同的两个对象可以快速进行比较得到结果
缺点:这种方法有限制就是当两个对比的对象中key的顺序不是完全相同时会比较出错
② 方法二:
// 对Object扩展一个方法
chargeObjectEqualObject.prototype.chargeObjectEqual = function(obj){
// 当前Object对象
var propsCurr = Object.getOwnPropertyNames(this);
// 要比较的另外一个Object对象
var propsCompare = Object.getOwnPropertyNames(obj);
if (propsCurr.length != propsCompare.length) {
return false;
}
for (var i = 0,max = propsCurr.length; i < max; i++) {
var propName = propsCurr[i];
if (this[propName] !== obj[propName]) {
return false;
}
}
return true;
}
getOwnPropertyNames该方法可以将Object对象的第一层key获取到并返回一个由第一层key组成的数组。
优点:相对方法一进行了优化,可以应对不同顺序的Object进行比较,不用担心顺序不同而对比出错
缺点:从方法中可以看到只能获取到第一层的key组成的数组,当对象是复合对象时无法进行多层对象的比较
③ 方法三:
function deepCompare(x, y) {
var i, l, leftChain, rightChain;
function compare2Objects(x, y) {
var p;
// remember that NaN === NaN returns false
// and isNaN(undefined) returns true
if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
return true;
}
// Compare primitives and functions.
// Check if both arguments link to the same object.
// Especially useful on the step where we compare prototypes
if (x === y) {
return true;
}
// Works in case when functions are created in constructor.
// Comparing dates is a common scenario. Another built-ins?
// We can even handle functions passed across iframes
if ((typeof x === 'function' && typeof y === 'function') ||
(x instanceof Date && y instanceof Date) ||
(x instanceof RegExp && y instanceof RegExp) ||
(x instanceof String && y instanceof String) ||
(x instanceof Number && y instanceof Number)) {
return x.toString() === y.toString();
}
// At last checking prototypes as good as we can
if (!(x instanceof Object && y instanceof Object)) {
return false;
}
if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
return false;
}
if (x.constructor !== y.constructor) {
return false;
}
if (x.prototype !== y.prototype) {
return false;
}
// Check for infinitive linking loops
if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
return false;
}
// Quick checking of one object being a subset of another.
// todo: cache the structure of arguments[0] for performance
for (p in y) {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
} else if (typeof y[p] !== typeof x[p]) {
return false;
}
}
for (p in x) {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
} else if (typeof y[p] !== typeof x[p]) {
return false;
}
switch (typeof(x[p])) {
case 'object':
case 'function':
leftChain.push(x);
rightChain.push(y);
if (!compare2Objects(x[p], y[p])) {
return false;
}
leftChain.pop();
rightChain.pop();
break;
default:
if (x[p] !== y[p]) {
return false;
}
break;
}
}
return true;
}
if (arguments.length < 1) {
return true; //Die silently? Don't know how to handle such case, please help...
// throw "Need two or more arguments to compare";
}
for (i = 1, l = arguments.length; i < l; i++) {
leftChain = []; //Todo: this can be cached
rightChain = [];
if (!compare2Objects(arguments[0], arguments[i])) {
return false;
}
}
return true;
}
深度对比两个对象是否完全相等,可以封装成一个组件方便随时调用。
21、编写一个可以执行如下操作的函数
var addSix=createBase(6);
addSix(10) //16
addSix(21) //27
22、判断一个字符串中出现次数最多的字符,统计这个次数
一、
var str = 'asdfssaaasasasasaa';
var json={
};
for(var i=0;i<str.length;i++){
if(!json[str.charAt(i)]){
json[str.charAt(i)]=1;
}else{
json[str.charAt(i)]++;
}
}
var iMax=0;
var iIndex='';
for(var i in json){
if(json[i]>iMax){
iMax=json[i];
iIndex=i;
}
}
console.log('出现次数最多的是:'+iIndex+'出现'+iMax+'次');
二、
let str = 'asfjasiofoivnoi';
function count(str){
let obj={
},
arr = str.split('');
//遍历数组
arr.forEach(function(val,index){
//将数组的元素存入对象,初始值为1,如果后面遍历的元素之前已存在就+1
if(obj[val]){
obj[val]+=1;
}else{
obj[val]=1
}
})
//遍历对象中的属性(字符),值(出现的次数)
let num=0,
res;
for(let i in obj){
if(num<obj[i]){
//将最多的次数赋给num
num=obj[i];
//最多次数的属性(字符串)赋给res
res=i;
}
}
console.log('最多的字符串是'+res+', 出现次数是'+num);
}
count(str);
23、数组操作,数组合并方法
https://www.jianshu.com/p/22a4c0b514fa
1、concat()
2、for循环
大概的思路是:遍历其中一个数组,把该数组中的所有元素依次添加到另外一个数组中
3、apply
函数的apply方法有一个特性,那就是func.apply(obj,argv),argv是一个数组。所以我们可以利用这点,直上代码:
a.push.apply(a,b);
24、闭包是什么?应用场景
能够读取其他函数内部变量的函数。
或简单理解为定义在一个函数内部的函数,内部函数持有外部函数内变量的引用。
4、闭包用途
1、读取函数内部的变量
2、让这些变量的值始终保持在内存中。
3、方便调用上下文的局部变量。利于代码封装。
https://www.cnblogs.com/Renyi-Fan/p/11590231.html
25、编写一个方法parseQueryString,它的用途是把URL请求参数解析为一个对象
function parseQueryString(argu){
var str = argu.split('?')[1];
var result = {
};
var temp = str.split('&');
for(var i=0; i<temp.length; i++)
{
var temp2 = temp[i].split('=');
result[temp2[0]] = temp2[1];
}
return result;
}
26、编写一个方法去掉数组里的重复内容var arr=[1,2,3,4,5,6,7,1,3],然后将该数组打乱
function shuffle(arr) {
var newArr=[];
arr.forEach((item,index)=>{
if(newArr.indexOf(item)==-1){
newArr.push(item)
}
})
for (let i=newArr.length-1; i>=0; i--) {
let rIndex = Math.floor(Math.random()*(i+1));
let temp = newArr[rIndex];
newArr[rIndex] = newArr[i];
newArr[i] = temp;
}
return newArr;
}
27、宏任务和微任务
https://blog.csdn.net/dream2222222222/article/details/103627935
宏任务一般是:包括整体代码script,setTimeout,setInterval。
微任务:Promise,process.nextTick
1、v-if和v-show区别
v-if:当隐藏结构时该结构会直接从整个dom树中移除;
v-show:当隐藏结构时是在该结构的style中加上display:none,结构依然保留。
什么时候使用v-if,什么时候使用v-show?
当组件中某块内容只会显示或隐藏不会被再次改变显示状态,此时用v-if更加合适,例如请求后台接口通过后台数据控制某块内容是否显示或隐藏,且这个数据在当前页不会被修改;
当组件某块内容显示隐藏是可变化的,此时用v-show更加合理,例如页面中有一个toggle按钮,点击按钮来控制某块区域的显示隐藏。
为什么这么说呢?大家都知道频繁操作dom对性能影响很大,v-if如果用在有toggle按钮控制的情况下,相当于在频繁增加dom和删除dom,所以此时用v-show更加合适
2、vuex原理及具体实现
https://baijiahao.baidu.com/s?id=1618794879569468435&wfr=spider&for=pc
3、vue双向数据绑定原理
https://m.php.cn/article/417685.html
4、导航守卫用法
导航守卫包括全局导航守卫和局部导航守卫
全局守卫
vue-router全局有三个守卫
全局前置守卫
一个简单的全局导航守卫的例子
import Vue from 'vue'import Router from 'vue-router'import route from './router'
Vue.use(Router)
const router = new Router({
routes: route // 路由列表})
// 模拟用户登录与否const HAS_LOGIN = true;
// 全局前置守卫// 在Router实例上进行守卫
router.beforeEach((to, from, next) => {
// to和from都是路由实例
// to:即将跳转到的路由
// from:现在的要离开的路由
// next:函数
// 如果未登录,就跳到登录页,如果登录了,选择哪个页面跳到哪个页面;如果登录了还去了login页面,就跳到首页。
if (to.name !== 'login') {
if (HAS_LOGIN) next()
else next({
name: 'login' })
} else {
if (HAS_LOGIN) next({
name: 'home' })
else next()
}})
// 全局解析守卫
router.beforeResolve((to,from.next) => {
})
// 全局后置钩子
router.afterEach((to,form) => {
})
export default router
其中next()函数有几个取值
一定要确保调用next()方法。
路由独享的守卫
如果不想在全局配置路由的话,可以为某些路由单独配置守卫
比如:给home页面单独配置守卫
{
path: '/',
name: "home",
component: Home,
// 路由独享守卫
beforeEnter: (to, from, next) => {
if(from.name === 'about'){
alert("这是从about来的")
}else{
alert("这不是从about来的")
}
next(); // 必须调用来进行下一步操作。否则是不会跳转的
}
}
路由组件内的守卫
在Home.vue页面中举个例子
export default {
// 组件内守卫
// 因为这个钩子调用的时候,组件实例还没有被创建出来,因此获取不到this
beforeRouteEnter (to, from, next) {
console.log(to.name);
// 如果想获取到实例的话
// next(vm=>{
// // 这里的vm是组件的实例(this)
// });
next();
},
// 路由即将要离开的时候调用此方法
// 比如说,用户编辑了一个东西,但是还么有保存,这时候他要离开这个页面,就要提醒他一下,还没保存,是否要离开
beforeRouteLeave (to, from, next) {
const leave = confirm("确定要离开吗?");
if(leave) next() // 离开
else next(false) // 不离开
},}
对于beforeRouteUpdate的演示例子
beforeRouteUpdate(to,from,next){
console.log(to.name, from.name);
next();
},
beforeRouteUpdate被触发的条件是:当前路由改变,但是该组件被复用的时候。
比如说:argu/fu1到argu/f2这个路由,都复用了arg.vue这个组件,这个时候beforeRouteUpdate就会被触发。可以获取到this实例
一个完整的导航解析流程
1、导航被触发。
2、在失活的组件(即将离开的页面组件)里调用离开守卫。 beforeRouteLeave
3、调用全局的 beforeEach 守卫。
4、在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
5、在路由配置里调用(路由独享的守卫) beforeEnter。
6、解析异步路由组件
7、在被激活的组件(即将进入的页面组件)里调用 beforeRouteEnter。
8、调用全局的 beforeResolve 守卫 (2.5+)。
9、导航被确认。
10、调用全局的 afterEach 钩子。所有的钩子都触发完了。
11、触发 DOM 更新。
12、用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
5、axios请求拦截
// 请求拦截(配置发送请求的信息)
axios.interceptors.request.use(function (config) {
// 处理请求之前的配置
return config
}, function (error) {
// 请求失败的处理
return Promise.reject(error)
})
// 响应拦截(配置请求回来的信息)
axios.interceptors.response.use(function (response) {
// 处理响应数据
return response
}, function (error) {
// 处理响应失败
return Promise.reject(error)
})
// axios转发 //其他页面在使用axios的时候直接 this.$axios就可以了
Vue.prototype.$axios = axios
6、vue自定义组件
全局组件
定义方式示例:
Vue.component("hello-component",{
props:["message"],
template :"组件定义之全局组件
{
{message}}
"
});
局部组件
定义方式示例:
var limitComponent = {
props:["message"],
template :"{
{message}}
"
}
new Vue ({
el : "#app",
components :{
"child-component": limitComponent
}
});
7、组件传值
https://blog.csdn.net/zhanghl150426/article/details/90665423
https://segmentfault.com/a/1190000018120586
8、vue如何获取dom元素
获取dom元素可以使用
elementList = document.querySelectorAll(selectors);//获取多个dom元素 如ul中的li
element = document.querySelector(selectors)//获取dom元素中的第一个元素
在vue中使用
mounted(){
//这里必须是mouted钩子
this.title = document.querySelector('#footer-box-title');
this.title.style.color = "#ff0000";
}
要在mounted中使用,因为只有在执行mounted的时候,vue已经渲染了dom节点,这个时候是可以获取dom节点的,vue中尽量不去操作dom元素,选用
ref操作属性获取
<button ref="btn">获取ref</button>
this.$refs.btn.style.backgroundColor="#ff0000"
9、data为什么是一个函数而不是一个对象
如果两个实例引用同一个对象,当其中一个实例的属性发生改变时,另一个实例属性也随之改变,只有当两个实例拥有自己的作用域时,才不会相互干扰。
这是因为JavaScript的特性所导致,在component中,data必须以函数的形式存在,不可以是对象。
组件中的data写成一个函数,数据以函数返回值的形式定义,这样每次复用组件的时候,都会返回一份新的data,相当于每个组件实例都有自己私有的数据空间,它们只负责各自维护的数据,不会造成混乱。而单纯的写成对象形式,就是所有的组件实例共用了一个data,这样改一个全都改了。
10、compute和watch区别,应用场景
computed是一个计算属性,是实时响应的,只要data中的属性发生了变化那么就会触发computed,
计算属性是基于属性的依赖进行缓存的,methods调用的时候需要加(),而computed调用的时候是不需要加()
watch属性监听,watch用来监听属性的变化,当值发生变化的时候来执行特定的异步函数,
watch监听属性的时候会有2个参数newVal和oldVal一个新值一个旧值
11、生命周期函数
beforeCreate
1 .实例初始化之后
2 .this指向创建的实例
3 .数据观测,event/watcher配置尚未完成
4 .不能访问到methods,data,computed,watch上的方法和数据
5 .console.log(this),上面说的这些是都能打印出来的,但是访问不到
created
1 .实例创建完成
2 .数据观测,属性和方法的运算,watch/event事件的回调
3 .可以调用methods中定义的方法,修改data的数据
4 .触发响应式变化
5 .computed值重新计算
6 .watch等变更的监测
7 .进行ajax请求,并对实例进行初始化预处理
8 .在vue-router中,进入created生命周期阶段之后无法对挂载实例进行拦截的,此时实例已经创建完成。如果需要某些数据获取完成才允许进入页面的场景,建议在路由钩子beforeRouterEnter中实现
9 .还未挂在到DOM,不能访问el属性,ref属性内容为空
beforeMount
1 .在挂载之前被调用
2 .寻找对应的template,并编译成render函数,如果指定了render函数,直接采用render函数,则会直接使用
3 .如果没有el属性,生命周期暂停,等待vm. m o u n t ( e l ) 调 用 才 继 续 m o u n t e d 1. 实 例 挂 载 到 d o m 上 , 此 时 可 以 通 过 D O M a p i 获 取 到 D O M 节 点 , mount(el)调用才继续 mounted 1 .实例挂载到dom上,此时可以通过DOM api获取到DOM节点, mount(el)调用才继续mounted1.实例挂载到dom上,此时可以通过DOMapi获取到DOM节点,ref属性可以访问
2 .mounted不会承诺所有子组件也一起被挂载,如果需要定位到整个视图都被渲染完毕,可以使用vm.$nextTick
beforeUpdate
1 .这里更新的对象是模板,即虚拟dom重新渲染和打补丁,发生在以上两个流程之间
2 .生成新的虚拟dom
3 .如果发生变更的数据没有被使用,是不会触发更新流程的
4 .避免在这个函数内进行数据操作,可能会引起死循环,this.a++
updated
1 .由于数据更改导致的虚拟dom重新渲染和打补丁,在这歌之后会调用这个钩子
2 .此时组件dom已经更新,可以执行依赖于DOM的操作
3 .避免在这个函数中操作数据
beforeDestory
1 .实例销毁之前调用,实例仍然完全可用,this仍然可以获取到实例
2 .销毁定时器,解绑全局事件,销毁插件对象
destoryed
1 .销毁之后调用,所有的东西都会解绑,所有的事件监视器都会被移除,子实例也会被销毁
12、v-for和v-if的优先级,为什么不推荐一起使用
建议不要在与v-for相同的元素上使用v-if。因为v-for指令的优先级高于v-if当它们处于同一节点。v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。
13、使用了scope属性,如何不动子组件代码修改子组件的样式
深选择器
如果想对设置了scoped的子组件里的元素进行控制可以使用 ’>>>’ 或者 ’deep’
14、 编程式导航使用的方法以及常用的方法
router.push(location)
想要导航到不同的 URL,则使用 router.push 方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。 当你点击
时,这个方法会在内部调用,所以说,点击
等同于调用router.push(…)。这样我们想要路由跳转到指定页面是,就可以在js中用这个方法,这个方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:
router.push({ name: ‘user’, params: { userId: 123 }})
声明式:
编程式:router.push(…)
router.replace(location)
跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。 声明式:
编程式:router.replace(…)
router.go(n)
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)。正数表示前进,负数表示后退。所以我这里让路由返回用了this.$router.go(-1)。
为什么不用push或者replace呢? 是因为当我修改完了之后,路由自动跳转回去,但是这时用户再手动返回一次,就会出现很不好的体验,push是会返回上个信息修改页面,replace是会返回到修改信息的上一层级页面,因为当前层级已经被代替掉了
15、vue重置date数据
…
data() {
return {
name: ‘’,
sex: ‘’,
desc: ‘’
}
}
…
// 逐个赋值
this.name = ‘’
this.sex = ‘’
this.desc = ‘’
MDN关于该方法的介绍:Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
用法: Object.assign(target, …sources)
第一个参数是目标对象,第二个参数是源对象,就是将源对象属性复制到目标对象,返回目标对象
其中就是将一个对象的属性copy到另一个对象
vue中:
this. d a t a 获 取 当 前 状 态 下 的 d a t a t h i s . data 获取当前状态下的data this. data获取当前状态下的datathis.options.data() 获取该组件初始状态下的data
所以,下面就可以将初始状态的data复制到当前状态的data,实现重置效果:
Object.assign(this. d a t a , t h i s . data, this. data,this.options.data())
当然,如果你只想重置data中的某一个对象或者属性:
this.form = this.$options.data().form
16、vue设置和删除对象属性
一、$set = >添加属性
<div id="itany">
<button @click="doAdd">添加属性</button>
<h2>{
{
user.age}}</h2></div>var vm = new Vue({
el: '#itany',
data: {
user: {
id: 1,
name: 'zhang'
}
},
methods: {
doAdd() {
this.$set(this.user, 'age', 18); //通过vue实例的$set方法为对象添加属性,可以实时监视
Vue.set(this.user, 'age', 22); // Vue.set(this.user,'age',22);全局方式也可以设置
//if (this.user.age) {
// this.user.age++;
//} else {
// Vue.set(this.user, 'age', 1);
//}小判断,如果user对象存在age属性,就加一,否则添加属性age并赋值1
},
}});
二、$delete = >删除属性
<button @click="doDelete">删除属性</button>
doDelete() {
if (this.user.age) {
Vue.delete(this.user, 'age'); //全局方式
}}
17、history和hash区别
hash模式,带#号,坏处就是,例如比如生成二维码的时候会自动过滤掉#后面的参数,微信登录,已经分享的时候都会把#后面的参数或者路径都过滤掉,这样我们就很不方便了,导致一些Bug的产生,所以要用到history模式。
history利用浏览器的history.pushState API来跳转无需加载页面,当使用history模式时,URL就是正常的URL,如http://www.weiyunquan.com/user/id,这样比较美观好看正常。不过这种模式需要服务端的配置支持,如Nginx配置
1、生命周期函数
2、组件传值
https://segmentfault.com/a/1190000020074507
3、虚拟dom
虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能。
用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中,当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异把 2 所记录的差异应用到步骤 1 所构建的真正的 DOM 树上,视图就更新了。
react diff 原理(常考,大厂必考)
4、redux实现
https://github.com/brickspert/blog/issues/22
5、组件创建方式,有什么区别
https://www.cnblogs.com/soyxiaobi/p/9573767.html
6、高阶组件
https://react.docschina.org/docs/higher-order-components.html
高阶组件是一个以组件为参数并返回一个新组件的函数。HOC 运行你重用代码、逻辑和引导抽象。最常见的可能是 Redux 的 connect 函数。除了简单分享工具库和简单的组合,HOC 最好的方式是共享 React 组件之间的行为。如果你发现你在不同的地方写了大量代码来做同一件事时,就应该考虑将代码重构为可重用的 HOC。
7、性能优化
8、setState方法是异步更新数据还是同步
在代码中调用 setState 函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发所谓的调和过程(Reconciliation)。经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个 UI 界面。在 React 得到元素树之后,React 会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染。在差异计算算法中,React 能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。
由React控制的事件处理程序,以及生命周期函数调用setState不会同步更新state 。
React控制之外的事件中调用setState是同步更新的。比如原生js绑定的事件,setTimeout/setInterval等。
大部分开发中用到的都是React封装的事件,比如onChange、onClick、onTouchMove等,这些事件处理程序中的setState都是异步处理的。
9、key有什么作用
Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。
render () {
return (
<ul>
{
this.state.todoItems.map(({
item, key}) => {
return <li key={
key}>{
item}</li>
})}
</ul>
)}
在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系,因此我们绝不可忽视转换函数中 Key 的重要性。
1、页面跳转方案
1、switchTab
//只能跳转到tabBar配置页面
wx.switchTab({
url: '/pages/index/index',});
2、navigateBack
//返回上一级页面(delta:返回的页面数,如果 delta 大于现有页面数,则返回到首页,默认值为1)
wx.navigateBack({
delta: 2})
3、redirectTo
//关闭当前页面,跳转到应用内的某个页面
wx.redirectTo({
url: '/pages/index/index',});
4、navigateTo
//保留当前页面,跳转到应用内的某个页面
wx.navigateTo({
url: '/pages/index/index',});
5、reLaunch
// 关闭所有页面,打开到应用内的某个页面
wx.reLaunch({
url: '/pages/index/index',});
2、生命周期函数
/**
/**
/**
/**
/**
3、上拉加载和下拉刷新实现
上拉加载:onReachBottom(){ }
下拉刷新:onPullDownRefresh(){ }
4、页面传值
1、使用navigator的url带参传值
(1)在pageA页面有一个固定的值要传递到pageB页面,比如说一个固定的值user_id要传递给B
index4
2、利用getCurrentPages进行页面传值
(1).getCurrentPages()获取当前页面栈。数组中第一个元素为首页,最后一个元素为当前页面。
3. globalData全局对象
4、数据缓存
5、遇到过什么问题,相应地解决方案
https://blog.csdn.net/dream2222222222/article/details/103167757