前端相关知识总结
Opened this issue · 0 comments
HTML & CSS
一,css常用布局
- W3c标准盒模型:width = content的宽 height = content的高
- IE怪异盒模型:width = content的宽 + (border+padding)*2 height = content的高 +(border+padding)*2
box-sizing:以特定的方式定义匹配某个区域的特定元素
1,content-box:在宽度和高度之外绘制元素的内边距和边框(w3c标准)
2,border-box:内边距和边框在已设定的宽度和高度内绘制,通过从宽高减去边框和内边距才能得到内
容的宽高(IE盒模型),这种方式更受开发者和设计师喜欢,不用担心变形
3,inherit:从父元素继承box-sizing属性的值
- 定位布局:position属性的四个值:absolute/fixed/relative/static/inherit【绝对定位,相对于static定位以外的第一个父元素定位//绝对定位,相对于浏览器窗口定位/相对定位,不脱离文档流,
可以用left等定位//默认值,在正常的流中,忽略left,right,top,bottom,z-index//继承父元素的position属性】,然后通过(left,top,right,bottom)来调整位置 - 流布局:网页中主要划分区域的尺寸使用百分数(搭配min-,max-使用)例如,设置网页主体的宽度为80%,min-width为960px。图片也作类似处理(width:100%, max-width一般设定为图片本身的尺寸,防止被拉伸而失真)
- 浮动布局:使用 float:left/right,使元素脱离文档流,所以在其后面的元素会受影响
1,早期会在浮动元素后面加一个空标签<div class="clear"></div> .clear{clear:both}
2,对float的直接父元素添加一个 clearfix类
.clearfix:after{content:'';display:table;clear:both}
.clearfix:{*zoom:1;}
- flex:弹性布局
- grid
- 圣杯布局
- 响应式布局:一个网站能够兼容多个终端。使用css的媒体查询;使用meta标签设置屏幕按1:1尺寸显示,可以禁止用户缩放页面;宽度不固定,使用百分比
二,BFC(用来块级格式化上下文)
是一个环境,html元素在这个环境中按照一定规则进行布局,一个环境中的元素不会影响到其它环境中的布局。
满足下列css声明之一的元素会生成BFC:
- float的值不为none
- overflow的值不为visible
- position的值不为static
- display的值为inline-block,table-cell,table-caption,flex,inline-flex
BFC的布局规则:
- 内部的元素会在垂直方向一个接一个地排列,可以理解为是BFC中的一个常规流
- 元素垂直方向的距离由margin决定,即属于同一个BFC的两个相邻盒子的margin可能会发生重叠
- 每个元素的左外边距与包含块的左边界相接触(从左往右,否则相反),即使存在浮动也是如此,这说明BFC中的子元素不会超出它的包含块
- BFC的区域不会与float元素区域重叠
- 计算BFC的高度时,浮动子元素也参与计算
- BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素,反之亦然
BFC的应用
- BFC会导致外边距折叠,“属于同一个BFC的两个相邻元素的margin会发生重叠”;理论上两个兄弟元素之间的边距应该是两个元素边距之和(20px),但实际是10px,当两个兄弟元素
的外边距不一样时,以最大的外边距为准
<div class="container">
<p>sibling 1</p>
<p>sibling 2</p>
</div>
.container {
background-color: red;
overflow: hidden; /* creates a block formatting context */
}
p {
background-color: lightgreen;
margin: 10px 0;
}
使用BFC来防止外边距折叠,当兄弟元素属于不同的BFC时,他们之间将不会有外边距折叠
2.使用BFC解决浮动问题:给父元素设置overflow:hidden可以清除子元素的浮动,触发了BFC,使内部的元素不会影响到外面的布局,BFC把浮动的子元素高度当做自己内部的高度去处理溢出,从外面看起来就是清除了浮动。
3.创建自适应两栏布局,并解决侵占浮动的问题
<div class="slide1"></div>
<div class="slide2"></div>
.slide1{
width:200px;
height:500px;
float:left;
}
.slide2{
height:500px;
}
上面情况就已经创建自适应两栏布局,如果把slide1换成图片,slide2换成文字,这时候文字会围绕着图片,如果不想要这种效果,可以为文字添加样式overflow:hidden;
三,关于居中
- 在已知宽度的情况下,可以通过设置margin的左右值为auto实现水平居中;
- 已知宽度和高度,用负margin法(脱离文档流)
<div class="container">
<div class="inner">子元素</div>
</div>
.container{
position:relative;
width:500px;
height:400px;
}
.inner{
position:absolute;
width:400px;
height:300px;
left:50%;
height:50%;
margin-left:-200px;
margin-top:-150px;
}
- table-cell (未脱离文档流,不知道子元素的宽高)
.container{
width:500px;
height:400px;
display:table-cell;
text-align:center;
vertical-align:middle;
}
- flex弹性盒子(不知道子元素的宽高)
.container{
width:500px;
height:400px;
display:flex;
align-item:center;
justify-content:center;
}
- transform实现(宽高未知)
.container{
position:relative;
width:500px;
height:400px;
}
.inner{
position:absolute;
left:50%;
top:50%;
transform:translate(-50%,-50%);
}
- 未知宽高,兼容IE浏览器,添加一个无用的标签,设置宽度为0
<div class="container">
<span></span>
<div class="inner"></div>
</div>
.container{
width:500px;
height:400px;
text-align:center;
}
span{
display:inline-block;
width:0;
height:100%;
vertical-align:middle;
zoom:1; /*BFC*/
*display:inline; /*IE6,7不支持 inline-block*/
}
.inner{
display:inline-block;
zoom:1;
*display:inline;
}
四,css中的单位
- px:相对单位,取决于屏幕的分辨率,分辨率越大,1px所占的就越小
- pt(point):绝对单位,1pt = 1/72英寸 = 0.39/72厘米,一般用于已经明确知道的设备中
- em:相对单位,是相对于其父元素的文本尺寸
<div style="font-size:16px;">
<div style="font-size:0.2em;">
<p style="font-size:2em"></p>
</div>
</div>
的文本尺寸为 16px\*0.2\*2 4. rem:相对单位,不止对字体大小有用,r就代表root,是基于根元素进行设置,大多数情况根元素为 html ``` html{font-size:14px}
//28px ``` 可以通过修改根元素的大小来适配不同分辨率的设备 5. vh:基于窗口高度大小的单位,1vh = viewport高度的1/100,例如浏览器的高是900px,1vh=9px 6. vw:基于窗口宽度大小的单位,1vw = viewport宽度的1/100 7. vmin:取决于宽度和高度的最小值,例如:浏览器:1100(宽)\*750(高),1vmin = 7.5px 1vmax=11px 8. vmax:取决于宽度和高度的最大值,这两个单位可以让你非常灵活的利用可视窗口的大小 9. ex:小写字母x的高度 10. ch:数字0的宽度,ex,ch都依赖于font-size,同时还依赖于字体的font-family,这种单位大多数用于排版的微调,例如:上标,下标等五,CSS3动画和JS动画的优劣(推荐用css3动画来代替js动画)
-
js动画会占用主线程,而主线程中还有很多需要执行的js脚本,可能会导致线程阻塞,造成丢帧;
-
js动画代码的复杂度要高于css3动画
-
js动画效果比css3丰富,且对动画的开始,暂停,回放等执行能力强,没有兼容性的问题
-
CSS3存在兼容性的问题,且对动画的控制能力弱,不能绑定事件,对于复杂的动画,代码冗长,比较笨重
-
可以利用硬件来加速(通过GPU来提高动画性能)
-
浏览器可以对动画做优化(元素不可见时部动画,减少对FPS的影响)
-
对于帧速表现不好的低版本浏览器,css3可以做到自然降级,而js则需要编写额外的代码
-
css 3D动画在js中是无法实现的
-
css 2D矩阵动画(指transform,缩放,变形,x,y轴等)效率高于js利用margin,left,top模拟的矩阵动画
-
css3其他常规动画属性的效率(height,width,opacity等)低于js模拟的动画
六,纯css画基本图形
.triangle{ //三角形
width:0;
height:0;
border-left:50px solid transparent;
border-right:50px solid transparent;
border-bottom:50px solid red;
}
//平行四边形
.paralle{
width:150px;
height:100px;
margin-left: 20px;
transform:skewX(20deg); //2D倾斜转换
background: red;
}
//梯形
.trapezoid{
width:100px; //与三角形的区别
height:0;
border-left:50px solid transparent;
border-right:50px solid transparent;
border-bottom:50px solid red;
}
//五边形
#pentagon {
position: relative;
width: 54px;
border-width: 50px 18px 0;
border-style: solid;
border-color: red transparent;
}
#pentagon:before {
content: "";
position: absolute;
height: 0;
width: 0;
top: -85px;
left: -18px;
border-width: 0 45px 35px;
border-style: solid;
border-color: transparent transparent red;
}
//六边形
#hexagon {
width: 100px;
height: 55px;
background: red;
position: relative;
}
#hexagon:before {
content: "";
position: absolute;
top: -25px;
left: 0;
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 25px solid red;
}
#hexagon:after {
content: "";
position: absolute;
bottom: -25px;
left: 0;
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-top: 25px solid red;
}
//五角星
#star-five {
margin: 50px 0;
position: relative;
display: block;
color: red;
width: 0px;
height: 0px;
border-right: 100px solid transparent;
border-bottom: 70px solid red;
border-left: 100px solid transparent;
-moz-transform: rotate(35deg);
-webkit-transform: rotate(35deg);
-ms-transform: rotate(35deg);
-o-transform: rotate(35deg);
}
#star-five:before {
border-bottom: 80px solid red;
border-left: 30px solid transparent;
border-right: 30px solid transparent;
position: absolute;
height: 0;
width: 0;
top: -45px;
left: -65px;
display: block;
content: '';
-webkit-transform: rotate(-35deg);
-moz-transform: rotate(-35deg);
-ms-transform: rotate(-35deg);
-o-transform: rotate(-35deg);
}
#star-five:after {
position: absolute;
display: block;
color: red;
top: 3px;
left: -105px;
width: 0px;
height: 0px;
border-right: 100px solid transparent;
border-bottom: 70px solid red;
border-left: 100px solid transparent;
-webkit-transform: rotate(-70deg);
-moz-transform: rotate(-70deg);
-ms-transform: rotate(-70deg);
-o-transform: rotate(-70deg);
content: '';
}
七,移动端1px问题(物理像素和设备像素是不等同的)
- 如果在一个元素上使用scale时会导致整个元素同时缩放,所以应该在该元素的伪元素下设置scale属性。(-webkit-min-device-pixel-ratio: 2)
.box::after {
display: block;
content: '';
border-bottom: 1px solid #000;
transform: scaleY(.5);
}
2.linear-gradient 线性渐变,上半部分为白色,下半部分为黑色,可以使用伪元素,避免去添加一个无意义的空元素(-webkit-min-device-pixel-ratio: 2)
.linear::after {
display: block;
content: '';
height: 1px;
background: linear-gradient(0, #fff, #000);
}
3.box-shadow 将纵坐标的shadow设置为0.5px即可(-webkit-min-device-pixel-ratio: 2)
.box {
box-shadow: 0 0.5px 0 0 #000;
}
4.viewport 通过meta视口标签根据不同的dpr对不同的页面进行缩放,dpr=2,页面缩放到一半,dpr=3,页面缩放到三分之一
const dpr = window.devicePixelRatio
const meta = document.createElement('meta') // 创建meta视口标签
meta.setAttribute('name', 'viewport') // 设置name为viewport
meta.setAttribute('content', `width=device-width, user-scalable=no, initial-scale=${1/dpr}, maximum-scale=${1/dpr}, minimum-scale=${1/dpr}`) // 动态初始缩放、最大缩放、最小缩放比例
JS
一,JS的数据类型 详解
基本数据类型:null, undefined, boolean, number, string
复杂数据类型:object
新增数据类型:Symbol, BigInt
大多数数据类型都是Object类型的实例,创建Object实例的方式:(1)new操作符(2)使用对象字面量表示
三大引用类型(是一种数据结构,用于将数据和功能组织到一起,也被称为对象定义):Object , Array, Function
数组的每一项可以用来保存任何类型的数据,数组的大小是可以动态调整的
①基本数据类型的值不可改变; -------------- ① 引用类型的值是可变的
②基本数据类型不可添加属性和方法; -------------- ② 引用类型可以添加属性和方法
③基本数据类型的赋值是简单赋值; -------------- ③引用类型的赋值是对象引用,两个变量指向同一个对象,任何的操作都会互相影响
④基本数据类型的比较是值的比较; -------------- ④ 引用类型的比较是引用的比较,比较两个对象保存在栈区的指向堆内存的地址是否相同
⑤基本数据类型存放在栈区,栈区包括了变量的标识符和变量的值; -----------⑤引用类型是同时保存在栈区和堆区中的
二,js判断数据类型的方法
- typeof 返回的类型都是字符串的形式,且可以判断function类型
typeof a == "string" //true
typeof a == String //false
typeof null; //Object
typeof undefined; //undefined
- 判断已知对象类型的方法instanceof 后面加数据类型,且大小写不能写错
c instanceof Array //注意A要大写
- 根据对象的constructor判断
c.constructor === Array
//注意:constructor在类继承时会出错
eg: function A(){} function B(){}
A.prototype = new B(); //A继承于B
var aObj = new A();
aObj.constructor === B //true
aObj.constructor === A //false
//而instanceof方法不会出现该问题,对象直接继承和间接继承都会输出true
//要解决这个问题通常是让对象的constructor手动指向自己,但同时指向父类的时候就不会报true了
- 通用方法,prototype,注意大小写
//调用object的toString()方法,该方法返回运行时this指向的对象类型,返回的类型格式为[object XXX]
//String,Number,Boolean,Undefined,Null,Function,Date,Array,RegExp,Error,HTMLDocument...基本上所有对象的类型都可以通过这个方法获取到
Object.prototype.toString.call(new RegExp()); // [object RegExp]
Object.prototype.toString.call(new Error()); // [object Error]
Object.prototype.toString.call(document); // [object HTMLDocument]
Object.prototype.toString.call(window); // [object global] window是全局对象global的引用
- 无敌无能方法,jquery.type();
三,js封装,继承,多态
- 封装:在面向对象语言中称为类,就是将数据或函数集合在一个单元里,封装的意义在于保护代码防止被破坏
//(1)原始方法:使用new关键字来创建一个对象,然后为对象动态添加属性和方法,如果需要多次创建对象,需要重复代码多次
var obj = new Object();
obj.name = 'Tom';
obj.showName = function(){
console.log(this.name);
}
//(2)工厂模式:
function createObj(name){
var obj = new Object();
//私有的
obj.name = name;
obj.showName = function(){
console.log(this.name);
}
}
var obj1 = createObj('Tom');
//(3)构造函数:
function Persion(name){
//公开
this.name = name;
this.showName = function(){
console.log(this.name);
}
}
var obj1 = new Persion('Tom');
//(4)原型方法:这种方法共用属性和方法,所以new的实例之间是相互影响的
function persion(){}
persion.prototype.name = 'Tom';
persion.prototype.showName = function(){
console.log(this.name);
}
var obj1 = new persion();
obj1.name = 'Marry';
var obj2 = new persion();
obj2.showName(); //Marry
- 继承
//首先定义一个父类
function Animal(name){
this.name = name || 'Tom';
this.showName = function(){
return this.name;
}
}
//原型方法
Animal.prototype.eat = function(food){
return food;
}
//(1)原型链继承:将父类的实例作为子类的原型。 不能实现多继承(继承多个父类),创建子类时,无法向父类构造函数传参
function Cat(){}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
var cat = new Cat();
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
//(2)构造继承:复制父类的实例属性给子类,没用到原型,就是利用call或者apply把父类中通过this指定的属性和方法复制(借用)到子类创建的实例中。
//可以实现多继承,call多个父类对象,创建子类实例时,可以向父类传递参数,实例不是父类的,只是子类的,只能继承父类的实例属性和方法,不能继承原型属性和方法
function Dog(name){
//继承了父类
Animal.call(this);
}
var dog = new Dog();
console.log(dog.eat('fish')); //报错 dog.eat is not a function
console.log(dog instanceof Animal); // false
console.log(dog instanceof Dog); // true
//(3)实例继承:为父类实例添加新的特性,作为子类实例返回。实例是父类的实例,不是子类的实例,不支持多继承
function Pig(name){
var small = new Animal();
small.name = name || 'Tom';
return small;
}
var pig = new Pig();
console.log(pig instanceof Animal); //true
console.log(pig instanceof Pig); //false
- 多态:(1)JS的函数不支持多态,事实上JS函数是无态的,对于同名函数,后面的会把前面的覆盖掉,js要实现函数重载,可以根据参数的个数来实现 arguments.length
实例题
function Foo() {
Foo.a = function() {
console.log(1)
}
this.a = function() {
console.log(2)
}
}
// 以上只是 Foo 的构建方法,没有产生实例,此刻也没有执行
Foo.prototype.a = function() {
console.log(3)
}
// 现在在 Foo 上挂载了原型方法 a ,方法输出值为 3
Foo.a = function() {
console.log(4)
}
// 现在在 Foo 上挂载了直接方法 a ,输出值为 4
Foo.a();
// 立刻执行了 Foo 上的 a 方法,也就是刚刚定义的,所以
// # 输出 4
let obj = new Foo();
/* 这里调用了 Foo 的构建方法。Foo 的构建方法主要做了两件事:
1. 将全局的 Foo 上的直接方法 a 替换为一个输出 1 的方法。
2. 在新对象上挂载直接方法 a ,输出值为 2。
*/
obj.a();
// 因为有直接方法 a ,不需要去访问原型链,所以使用的是构建方法里所定义的 this.a,
// # 输出 2
Foo.a();
// 构建方法里已经替换了全局 Foo 上的 a 方法,所以
// # 输出 1
四,this指向问题
this的指向在函数定义的时候是确定不了的,只有在函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是调用它的对象
function A(){
var user = 'sprout';
console.log(this.user); //undefined
console.log(this); //window
}
A();
var B = {
user:'sprout',
fn:function(){
console.log(this.user); //sprout 这里的this指向的是对象B,因为调用fn是通过B.fn()执行的
},
C:{
user:'wenyi',
fn:function(){
console.log(this.user);
}
}
}
B.fn();
window.B.fn(); //这里this并没有指向window,因为window是js的全局对象,创建变量实际是在给window添加属性
B.C.fn(); //'wenyi' 如果这个函数包含多个对象,尽管是被最外层的对象调用,this指向的是它上一级的对象
var D = B.C.fn;
D(); //undefined 这里this指向的是window,因为this指向的永远是最后调用它的对象,因为fn在赋给D变量的时候并没有执行
对于构造函数里的this,new关键字是可以改变this的指向。a是Fn创建的一个实例,此时,仅仅是创建,并没有执行,但调用函数Fn的
是对象a,所以this指向的就是a
function Fn(){
this.user = 'sprout';
}
var a = new Fn();
console.log(a.user); //sprout
特殊的,当this遇到return时,如果返回值是一个对象,那么this指向的就是返回的那个对象,如果返回值不是一个对象,那么this还是
还是指向函数的实例
function Fn(){
this.user = 'sprout';
return {}; //(1)
//或者 return function(){} (2)
//或者 return 1; (3)
}
var a = new Fn();
console.log(a.user); //(1)(2)undefined (3) sprout
我们可以通过call,apply,bind等方法自行改变this的指向
五,前端跨域问题
浏览器有同源策略,本域的js不能操作其他域的页面对象
1. 单向跨域:(一般用于获取数据)
(1)使用JSONP跨域
通过script标签引入的js是不受同源策略的限制的,因为script标签引入的文件内容是不能够被客户端的js获取到的,不会影响到被引用文件的安全但是通过ajax加载的文件内容是能够被客户端的js获取到的,所以
ajax必须遵守同源策略(注意:所谓的域跟js的存放服务器没有关系)我们可以通过script标签引入一个js,php,或者jsp等的文件,次文件返回一个js函数的调用
并且把参数传进去,这样在本域中就可以获取到其他域的数据
//本域:例如:baidu.com
<script>
function JSONP_getUsers(users){
console.dir(users);
}
</script>
//加载google.com的getUsers.php文件
<script src="http://www.google.com/getUsers.php"></script>
//getUsers.php文件
<?php
echo 'JSONP_getUsers(["paco","Tom","Marry"])'; //返回一个js函数的调用
?>
//可以像上面这样前后端把函数名约定好,也可以动态生成js脚本,然后传递一个参数给跨域服务器端
function handler(data){
console.log(data);
}
var script = document.createElement('script');
script.setAttribute('src','http://www.google.com/getUsers.php?callback=handler');
// 把script标签加入head,此时调用开始
document.getElementsByTagName('head')[0].appendChild(script);
//此时,服务器主要做的是拼接字符串
$data = ["paco","Tom","Marry"];
echo $_GET['callback']."(".jaon_encode($data).")";
JSONP的缺点:只支持get请求;如果给其他域传参,可以在url地址后面挂参数
<script src="http://www.google.com/getUsers.php?flag=do&time=1"></script>
(2)window.name
是全局变量,是当前窗口的名字;每个iframe都有包裹它的window,而这个window是top window的子窗口。window.name的神奇之处在于name值在不同的页面(甚至不同域名)加载后依旧存在(如果没修改,则值不会变化)并且可以支持非常长的name值(2MB)
(3)服务器代理
(4)CORS(Cross-origin resource sharing)跨域资源共享
它允许浏览器向跨源浏览器发出XMLHttpRequest请求,从而克服Ajax只能同源使用的限制。CORS需要浏览器和服务器同时支持,IE10以及其他所有浏览器都支持该功能
- 简单请求:
同时满足以下两大条件:- 请求方法是以下其中之一(HEAD,GET,POST)
- HTTP的头信息不超过以下几种字段:(a) Accept (b)Accept-Language (c)Content-Language (d)Last-Event-ID (e)Content-Type:application/x-www-form-urlencoded或者 multipart/form-data或者 text/plain
- 非简单请求:
2.双向跨域
(1)document.domain(两个iframe之间)
(2)location.hash(两个iframe之间)
(3)HTML5的postMessage(两个iframe之间或两个页面之间)
六,js内存泄漏和垃圾回收机制
- js垃圾回收机制,有两种策略:(1)标记清除;(2)引用计数
-
标记清除:垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记,然后,它会去掉环境中的变量的标记和被环境中的变量引用的变量的标记,此后,如果变量再被标记则表示此变量准备被删除。 2008年为止,IE,Firefox,opera,chrome,Safari的javascript都用使用了该方式;
-
引用计数:跟踪记录每个值被引用的次数,当声明一个变量并将一个引用类型的值(function,Array,object)赋给该变量时,这个值的引用次数就是1,如果这个值再被赋值给另一个变量,则引用次数加1。相反,如果一个变量脱离了该值的引用,则该值引用次数减1,当次数为0时,就会等待垃圾收集器的回收。
这个方式存在一个较大的问题就是循环引用,就是A对象包含一个指向B对象的指针,B对象也包含一个指向A的引用,这就会造成大量的内存得不到回收(内存泄漏)
因为它们的引用次数永远不可能为0.早期的IE版本(ie4-ie6)采用引用计数,闭包导致泄漏的一个原因就是这个算法的缺陷
IE中有一部分对象并不是原生js对象,例如,DOM,BOM中的对象是以COM对象的形式实现的,而COM对象的垃圾回收机制采用的是引用计数。因此,虽然IE的js引擎采用的是标记清除策略,但是访问COM对象依然是基于引用计数的。在IE中设计COM对象就会存在循环引用的问题
七,js闭包
首先要理解作用域,当f1函数里面嵌套着函数f2,f2可以访问到全局变量以及f1内部的局部变量,但是,我们无法从外部来访问到f1的局部变量,
我们可以通过把内部的函数f2返回,这样就可以访问内部局部变量,这就是闭包。
-
为什么需要变量->因为局部变量无法共享和长久保存,而全局变量可能造成变量污染,用闭包来长久的保存变量又不会造成全局污染
-
特点:(1)占用更多内存;(2)不容易被释放;
-
如何使用闭包:
- 定义外层函数,封装被保护的局部变量。
- 定义内层函数,执行对外部函数变量的操作。
- 外层函数返回内层函数的对象,并且外层函数被调用,结果保存在一个全局的变量中。
总之:内层函数对象被全局的变量引用,所以内层函数调用结束之后依然无法被内存回收,虽然占用了更多的内存空间,但这样可以持久的保存内部局部变量
//给每个li添加点击事件
var oli = document.getElementsByTagName('li');
for(var i=0;i<5;i++){
oli[i].click = function(){
console.log(i); //5 用for循环为为每个li绑定点击事件,循环结束后,此时i=5,当我们点击执行匿名函数时,就会输出5,这里每个匿名函数都引用了同一个变量i
}
}
//用闭包,给点击事件绑定的匿名函数传递i后立即执行返回一个内部匿名函数,此时num保存的就是当前的i值,这个内部的匿名函数一直保存着num的引用
//当执行点击事件时,内部返回的匿名函数会输出各自保存的num的引用的值
var oli = document.getElementsByTagName('li');
for(var i = 0;i < 5;i++){
oli[i].onclick = (function(num){
return function(){
console.log(num);
}
})(i);
}
八,DOM事件的绑定,DOM事件中target和currentTarget的区别
target:触发事件的某个具体的对象,只会出现在事件流的目标阶段
currentTarget: 绑定事件的对象,恒等于this,可能出现在事件流的任意阶段
通常情况下,target和currentTarget是一致的,但有一种情况必须区分,在父子嵌套的关系中,父元素绑定了事件,单击子元素,根据事件流,在不阻止事件流的前提下会传递至父元素,导致父元素的事件处理函数执行,
这时候,currentTarget指向的是父元素,因为它是绑定事件的对象,而target指向了子元素,因为他是触发事件的具体对象。
九,原生js ajax请求
(1)创建ajax对象(2)连接到服务器 (3)发送请求 (4)接收返回值
//将代码封装
function ajax(url,func){
if(window.XMLHttpRequest){
var oAjax = new XMLHttpRequest();
}else{
var oAjax = new ActiveXObject('Microsoft.XMLHTTP'); //IE6创建ajax对象
}
oAjax.open("GET",url,true); //open(方法,文件名,异步传输)
oAjax.send(); //发送请求 post请求发送的数据 oAjax.send('name=jack&age=18')
oAjax.onreadystatechange = function(){ //当readyState改变时就会执行
//readyState:存服务器响应的状态信息
//0: 请求未初始化(代理被创建,但尚未调用 open() 方法)
//1: 服务器连接已建立(open方法已经被调用)
//2: 请求已接收(send方法已经被调用,并且头部和状态已经可获得)
//3: 请求处理中(下载中,responseText 属性已经包含部分数据)
//4: 请求已完成,且响应已就绪(下载操作已完成)
if(oAjax.readyState == 4){
if(oAjax.status == 200){ //200表示OK,成功
func(oAjax.responseText); //处理返回的数据 responseText是字符串形式的响应数据
}
}
}
}
十,原型链,对象,构造函数之间的联系
十二,http状态码
200和304的区别
十三,设计模式
十四,Session,localStorage,cookie,SessionStorage
十五,浏览器缓存
十六,深拷贝实现原理
1.浅拷贝就是遍历对象属性
function shallowClone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
2.深拷贝可以分解成,浅拷贝+递归
用系统自带的JSON来做深拷贝JSON.parse(JSON.stringify(source)
内部做了循环检测,但是内部用递归的方式,会有溢出问题;不能复制对象中的函数;原型链里的属性无法拷贝;
十七,关于setTimeOut,Promise执行顺序
像settimeout、ajax等异步操作的回调,会进入”js事件单线程任务队列“中,而且只有主线程中没有执行任何同步代码的前提下,才会执行异步回调。
而settimeout(fn, 0)表示立即执行,也就是用来改变任务的执行顺序,要求浏览器”尽可能快“的进行回调。
Promise构造函数里的代码是同步执行的。then方法指向的回调将在当前脚本所有同步任务执行完后执行。
then比setTimeOut执行早的原因有两个:
- settimeout(fn, 0) 最小执行时间并不是0S,HTML5中已经将最小执行时间统一为4ms;
- Macrotasks和Microtasks 都属于异步任务中的一种,常用api分类:
macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering
microtasks: process.nextTick, Promise, MutationObserver
一个事件循环中只有一个macrotask任务,可以有一个或多个microtask任务。
setTimeout(function()` {
console.log(1)
}, 0);
new Promise(function(resolve, reject) {
console.log(2)
for (var i = 0; i < 10000; i++) {
if(i === 10) {console.log(10)}
i == 9999 && resolve();
}
console.log(3)
}).then(function() {
console.log(4)
})
console.log(5);
执行结果: 2 10 3 5 4 1
同步代码(包括promise的构造函数) -> promise.then -> setTimeout
十八, js链式调用原理
jQ链式调用原理,jQuery节点在调用api后都会返回节点自身
// 实现 add(1)(2)(3); //6
function add(num) {
var count = num;
var b = function(c){
count += c;
return b;
}
b.valueOf = function(){
return count;
}
return b;
}
十九
总结:从输入URL到页面展示,这中间发生了什么
- 用户输入url并回车
- 浏览器进程检查url,组装协议,构成完整的url
- 浏览器进程通过进程间通信(IPC)把url请求发送给网络进程
- 网络进程接收到url请求后检查本地缓存是否缓存了该请求资源,如果有则将该资源返回给浏览器进程
- 如果没有,网络进程向web服务器发起http请求(网络请求),请求流程如下:
进行DNS解析,获取服务器ip地址,端口(端口是通过dns解析获取的吗?这里有个疑问)
利用ip地址和服务器建立tcp连接
构建请求头信息
发送请求头信息
服务器响应后,网络进程接收响应头和响应信息,并解析响应内容 - 网络进程解析响应流程;
检查状态码,如果是301/302,则需要重定向,从Location自动中读取地址,重新进行第4步 (301/302跳转也会读取本地缓存吗?这里有个疑问),如果是200,则继续处理请求。
200响应处理:检查响应类型Content-Type,如果是字节流类型,则将该请求提交给下载管理器,该导航流程结束,不再进行后续的渲染,如果是html则通知浏览器进程准备渲染进程准备进行渲染。
准备渲染进程
浏览器进程检查当前url是否和之前打开的渲染进程根域名是否相同,如果相同,则复用原来的进程,如果不同,则开启新的渲染进程 - 传输数据、更新状态
渲染进程准备好后,浏览器向渲染进程发起“提交文档”的消息,渲染进程接收到消息和网络进程建立传输数据的“管道”
渲染进程接收完数据后,向浏览器发送“确认提交”
浏览器进程接收到确认消息后更新浏览器界面状态:安全、地址栏url、前进后退的历史状态、更新web页面 - 根据DOM树和css样式表 构建布局树
框架(Vue,React)
一,实现原理(virtual dom和diff算法)
VUE的Vdom是树状结构,其节点是Vnode,Vnode和浏览器中的Node是一一对应的,通过Vnode的elm属性可以访问到对应的Node
Vnode是纯粹的JS对象,操作它很高效,但是Vdom的变更最终会转换成DOM操作,为了实现高效的DOM操作,一套高效的虚拟DOM diff算法非常重要。
Vue的diff算法是仅在同级的Vnode之间做diff,递归的进行同级diff,最终实现整个DOM树的更新。
Diff算法:(1)简单的diff算法:逐个遍历newVdom的节点,找到它在oldVdom中的位置,如果找到了就移动对应的DOM元素,如果没找到就说明是新增的节点,则创建一个新的节点插入
,遍历之后如果oldVdom中还有没处理过的节点,则说明这些节点在newVdom中被删除了,删除他们即可。
(2)Vue中的Diff实现:对oldVdom和newVdom的起始和终点分别对应两个指针,Vue不断的对Vnode进行处理,处理过的节点vue会在oldVdom和newVdom中同时将它们标记为已处理
直到起点跟终点相遇。vue首先会将一些不需要做移动的DOM快速处理,缩小后续的操作范围。对于同类节点(例如两个div)Vue会直接复用DOM,不需要移动DOM.
整个diff分为两个部分:
[a]. 第一部分是循环,循环内部是一个分支逻辑,每次循环只会进入其中一个分支,每次循环会处理一个节点,处理过之后节点被标记,vue的做法是将节点标记为undefined
[b]. 循环结束后,可能newVdom或者oldVdom中还有未处理的节点,如果是newVdom中有未处理节点,则这些节点是新增节点,做新增处理。如果是oldVdom中有这类节点,则这些是需要删除的节点,相应在DOM树中删除之
整个过程是逐步找到Vdom的差异,然后将差异反应到DOM树上(patch),并且Vue的patch是即时的,并不是打包所有修改最后一起操作DOM,这点与React不同,React是将更新放入队列后集中处理。
现代浏览器对这样的DOM操作做了优化,并无差别。
二,MVC,MVVM
- MVC: Model-View-Controller,除了界面View以及简单的数据Model以外,大部分都在Controller里面,Controller负责显示界面,
响应用户操作,网络请求以及与Model交互等。因此这就使得Controller编辑复杂,难以维护。thinkPHP就是基于MVC设计的。
MVC的缺点:
- 开发者在代码中大量调用相同的 DOM API, 处理繁琐 ,操作冗余,使得代码难以维护。
- 大量的DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。
- 当 Model 频繁发生变化,开发者需要主动更新到View ;当用户的操作导致 Model 发生变化,开发者同样需要将变化的数据同步到Model中,这样的工作不仅繁琐,而且很难维护复杂多变的数据状态。
- MVVM 的出现,完美解决了上面的三个问题。Model-View-ViewModel,View和Model之间是没有直接联系的,通过ViewModel进行交互,Model和View之间的交互是双向的
View的数据变化会同步到Model中,而Model数据的变化也会立即反应到View上,而他们之间的同步工作是自动的,不需要手动去操作DOM
三,setState是异步的
#####(1)同步更新,效率会很低;
#####(2)保持内部一致性(state,props,refs),props只有组件重新渲染之后才会更新;
四,受控组件和非受控组件(主要是针对表单操作)
构建工具
一,webpack
入口文件怎么配置,多个入口怎么分割
loader配置,原理
webpack的原理和机制
Babel插件(transform-runtime,stage-2)的作用
webpack.optimize.UglifyJsPlugin这个插件压缩速度很慢,有没有什么办法提升速度
loader: 将某些类型的模块编译转换成其他类型模块
plugin:plugin是一个扩展器,它丰富了webpack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务
二,gulp
三,git
git flow
排序算法
ES6,ES7,ES8
一,babel把ES6转成ES5或者ES3的原理是什么
二,ES6箭头函数this问题
三,ES6拓展运算符
四,数组去重(最简洁)
五,let const原理(堆,栈)
V8内存分配和垃圾回收机制
CI / CD
CSS Modules
- 所有样式都是 local 的,解决了命名冲突和全局污染问题
- class 名生成规则配置灵活,可以此来压缩 class 名
- 只需引用组件的 JS 就能搞定组件所有的 JS 和 CSS
- 依然是 CSS,几乎 0 学习成本
前端模块 Commonjs ES6 Module
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
CommonJs 是单个值导出,ES6 Module可以导出多个
CommonJs 是动态语法可以写在判断里,ES6 Module 静态语法只能写在顶层
CommonJs 的 this 是当前模块,ES6 Module的 this 是 undefined
微信小程序
一,微信小程序性能优化
小程序打开过程: (1)资源准备(代码包下载)-> (2)业务代码的注入以及落地页首次渲染 -> (3)落地页数据请求时的loading
提升体验最直接的方法就是控制包的大小:
(1)上传代码时要压缩 (在开发者工具上勾选)
(2)清理无用的代码和资源文件
(3)减少图片的数量和大小,除icon外都上传到服务器
采用分包机制,将用户访问率高的页面放在主包里,将访问率低的页面放入子包里,按需加载;因为代码包有一个下载的过程,所以子包不宜过大。也可以在可能访问子包的页面进行子包预加载,可根据用户的网络类型控制,网络好才预加载。
首屏加载优化:
(1)异步请求可以在页面onload就加载
(2)对变动频率低的异步数据可以利用storage缓存数据进行初步渲染,然后再进行异步数据的更新,优化性能 在无网环境也可以使用
(3)列表页->详情页 可以把一些关键字带过来,或者用骨架屏站位
####二,小程序渲染原理
双线程下的界面渲染,小程序的逻辑层和渲染层是分开的两个线程。在渲染层,宿主环境会把WXML转化成对应的JS对象,在逻辑层发生数据变更的时候,我们需要通过宿主环境提供的setData方法把数据从逻辑层传递到渲染层,再经过对比前后差异,把差异应用在原来的Dom树上,渲染出正确的UI界面。
1, 避免频繁调用setData,每次调用都是进程间的通信
2, 数据通信的性能与数据量正相关,因而如果有一些数据字段不在界面中展示且数据结构比较复杂或包含长字符串,则不应使用setData来设置这些数据;
3, 与界面渲染无关的数据最好不要设置在data中,可以考虑设置在page对象的其他字段下
冷启动:首次打开 或小程序已被微信主动销毁后再次打开
热启动:已打开过小程序,在一定时间内再次打开
小程序js代码无法操作DOM对象,也无法直接操作wxml上的容器或组件,js代码和webview没有运行在同一个进程
三,为什么小程序运行快
- 安装包缓存
- 分包加载
- 独立渲染线程
- webView预加载
每次小程序进入,除了当前页面,Native会额外的预先加载一个WebView, 等打开页面时 用默认数据直接渲染,等请求数据回来时 再局部更新 - Native组件 (目前Native实现的组件有
四,OpenID, UnionID
(1) OpenID = 用户微信号 & 公众平台AppId (两个数据加密得到)
注意:对于同一个商城有公众号和小程序,他们的OpenID是不同的,那他们的微信号分别进入两个环境,要怎么识别是同一个用户呢?
(2) UnionID : 用户 再不同应用中的唯一标识
UnionID = 用户微信号 & 开放平台AppId (两个数据加密得到)
前提是各个公众平台需要先绑定同一个开放平台才能从各个平台中获取到同一个 UnionID
移动端适配方案
一,rem适配
- 布局等比例缩放,动态设置html的font-size 来改变rem的大小
读取devicePixelRatio(当前显示设备的物理像素分辨率与CSS像素分辨率之)动态设置viewport
viewport标签只对移动端浏览器有效,对PC端浏览器是无效的。
postcss-pxtorem将单位转化为 rem
设置rootValue: 37.5 UI设计图稿宽度是750px , 所以需要尺寸 / 2
vw 适配
- 用PostCSS的插件postcss-px-to-viewport 将px转为vw
{
loader: 'postcss-loader',
options: {
plugins: ()=>[
require('autoprefixer')({
browsers: ['last 5 versions']
}),
require('postcss-px-to-viewport')({
viewportWidth: 375, //视口宽度(数字)
viewportHeight: 1334, //视口高度(数字)
unitPrecision: 3, //设置的保留小数位数(数字)
viewportUnit: 'vw', //设置要转换的单位(字符串)
selectorBlackList: ['.ignore', '.hairlines'], //不需要进行转换的类名(数组)
minPixelValue: 1, //设置要替换的最小像素值(数字)
mediaQuery: false//允许在媒体查询中转换px(true/false)
})
]
}
移动端1px问题
原因: dpr = 物理像素 / css像素 对于dpr=2(iphone 8) 想要实现物理像素为1px 就需要css像素为 0.5 IOS 目前支持,但是安卓机不支持
解决:(1)使用边框图片;
(2)使用阴影 border-shadow
(3)使用伪元素
.setBorderAll{
position: relative;
&:after{
content:" ";
position:absolute;
top: 0;
left: 0;
width: 200%;
height: 200%;
transform: scale(0.5);
transform-origin: left top;
box-sizing: border-box;
border: 1px solid #E5E5E5;
border-radius: 4px;
}
}
(4)用js获取 dpr动态设置viewport (dpr = 3, initial-scale=0.3333333333333333)