CSS盒模型
盒模型包含了content,padding,border,margin
1.一个是标准模型:盒模型的宽高只是content的宽高
2.一个是IE模型(又叫怪异模型):盒模型的宽高是内容(content)+填充(padding)+边框(border)的总宽高。
如何统一模型:将模型的宽高都统一为内容(content)+填充(padding)+边框(border)的总宽高。
div{
box-sizing: border-box;
}
DOM事件流
JS中事件分为三个阶段: 事件捕获 --> 事件目标 --> 事件冒泡
栗子:给目标元素(text)绑定click事件,实际的(text)元素在捕获阶段不会接收到事件,意味着在捕获阶段,事件从document到<body>再到<div>后就停止了。下一个阶段是“处于目标阶段”,于是事件在(text)上发生,并在事件处理中被看成是冒泡阶段的一部分。最后,冒泡阶段发生,事件又传播回文档。
DOM层级越小,事件传播的消耗越小,在一定情况下也可以视为一种优化性能的手段
比较常见的冒泡现象:
<a href="http://www.sina.com" id='test'>sina</a></body>
<script>
document.onclick = function() {
alert('doc click');
return false;
}
var a = document.getElementById('test');
a.onclick = function() {
alert('a click');
}
</script>
正常执行顺序
1.从document慢慢找下去,判断当前元素是否是时间的触发源
2.找到事件源,执行a元素上的onclick事件
2.事件进入冒泡阶段,包含a元素的document的onclick事件
3.事件完成,进入浏览器默认行为,即跳转。
PS:如果当使用return false会打断后续行为,所以这里不会跳转。
使用event.preventDefault
取消浏览器默认行为
使用event.stopPropagation
会取消冒泡,但不取消浏览器默认行为。
CSS层叠规则(视图上的显示层级)
元素居中
水平居中
行内元素:text-align: center
块级元素:margin: 0 auto
absolute + transform
flex + justify-content: center
垂直居中
line-height: height
absolute + transform
flex + align-items: center
table
水平垂直居中
absolute + transform
flex + justify-content + align-items
CSS选择器问题
优先级:!important > 行内样式 > #id > .class > tag > * > 继承 > 默认
css选择器原理:采用递归方式,从右往左找,如body #box1 .box2 div span a
,
1.浏览器会先找所有的a
标签
2.从这些a
标签中找到父元素是span
的
3.从这些span
中找父元素是div
的
4.从这些div
中找父元素是.box2
的
........
优化问题:
1.层级越多需要找的时间越久,耗时,尽量直接写class或者id来找,层级顶多3层。
2.尽量不要直接写div span a
,因为页面上可能有很多a
标签,span
和div
,会更耗时。
清除浮动
只介绍以下两种我用的,其他不赘述
1.(推荐)给浮动元素的父元素加上clearfix
类
.clearfix{*zoom:1;}
.clearfix:after{display:block; content:"clear"; height:0; clear:both; overflow:hidden; visibility:hidden;}
2.给浮动元素结尾处添加一个空的div
,并且clear:both;
link 与 @import 的区别
1.link功能较多,可以定义 RSS,定义 Rel 等作用,而@import只能用于加载 css
2.当解析到link时,页面会同步加载所引的 css,而@import所引用的 css 会等到页面加载完才被加载,从体验来说,link要由于@import
3.@import需要 IE5 以上才能使用
4.link可以使用 js 动态引入,@import不行
javascript数据类型
值类型(基本类型):字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol。
引用数据类型:对象(Object)、数组(Array)、函数(Function)。
区别:
1.值类型变量是存放在栈区的(栈区指内存里的栈内存)
2.引用类型顾名思义只是引用,数据是存在堆内存中,如果内存中的值变化,那么引用它的变量也会变化
看下面栗子
var a = [1,2,3];
var b = a;
a.push(4);
console.log(b); // [1,2,3,4]
关于变量提升问题
JavaScript 中,函数及变量的声明都将被提升到函数的最顶部。
函数提升的优先级大于变量提升的优先级,即函数提升在变量提升之上
来看这一段代码
console.log(a,fun); // undefiend undefiend
var a = 1;
function fun(){}
那么,JS执行以上代码时,实际执行顺序是这样的
function fun(){}
var a;
console.log(a,fun);
a = 1;
函数声明和函数表达式的区别
- 函数声明
fn() //不会报错
function fn(){}
// 实际执行顺序为
function fn(){}
fn()
- 函数表达式 会将变量和函数拆分开来执行,存在变量提升问题
fn() //Uncaught TypeError:fn is not a function
var a = function(){}
// 实际执行顺序为
var a;
a();
a = function(){};
闭包
script标签
1.<script defer>
: 异步加载,元素解析完成后执行
2.<script async>
: 异步加载,与元素渲染并行执行
对于大多情况来说,script标签尽量放在html最底部引入进来,防止影响html以及css的加载,影响页面呈现的速度。
对象的深拷贝浅拷贝问题
讲一下简单实现原理
1.浅拷贝:便利对象source
的属性,然后赋值到新对象target
上,如果source
的某一个子属性是引用类型
的,那么target
复制的属性的内存地址与source
的的子属性指向同一内存地址。会出现如下问题:
//浅拷贝
function shallow(target, source) {
for (let i in source) {
target[i] = source[i];
}
return target;
}
var obj1 = {
name: 'obj1',
arr: [1, 2, 3]
},
obj2 = {};
//浅拷贝一次
shallow(obj2, obj1);
obj1.arr.push(4);
console.log(obj2.arr); // [1,2,3,4]
一些简单的浅拷贝方式:
// Object.assign
let a = {
age: 1
}
let b = Object.assign({}, a)
// ...运算符
let a = {
age: 1
}
let b = {...a}
2.深拷贝:在浅拷贝基础上,遇到引用类型的值需要再次执行浅拷贝,也就是递归执行浅拷贝
,就不会出现浅拷贝的问题
function deep(target, source) {
for (var i in source) {
// 含有引用类型
if (typeof source[i] === 'object') {
// 这里可能是数组或者对象
if (source[i].constructor == Array) {
target[i] = [];
} else {
target[i] = {};
}
//需要递归执行deep拷贝
deep(target[i], source[i]);
} else {
target[i] = source[i];
}
}
}
var obj1 = {
name: 'obj1',
arr: [1, 2, 3]
},
obj2 = {};
//浅拷贝一次
deep(obj2, obj1);
obj1.arr.push(4);
console.log(obj1.arr,obj2.arr); // [1,2,3,4] [1,2,3]
最简单的实现深拷贝
var obj1 = {
name: 'jsonObj',
arr: [1, 2, 3]
}
//先把对象序列化成一个字符串,再转成JSON对象,就新建了一份内存
//IE7不兼容
var obj2 = JSON.parse(JSON.stringify(obj1));
obj1.arr.push(4);
console.log(obj1.arr, obj2.arr); // [1,2,3,4] [1,2,3]
对于更多出现的情况请参考lodash拷贝
new到底干了什么
1.新生成一个对象obj
2.链接到原型: obj.__proto__ = 对应的构造函数.prototype
3.修改this执行: apply
4.返回新对象
function p(age){
alert(123123);
this.age = age;
this.getAge = function(){
alert(this.age);
}
}
// 实现一个new
function create() {
// 创建一个空的对象
let obj = new Object();
// 获得构造函数
let Con = [].shift.call(arguments);
// 链接到原型
obj.__proto__ = Con.prototype;
// 执行构造函数,修改this指向
let result = Con.apply(obj, arguments)
// 确保 new 出来的是个对象
return typeof result === 'object' ? result : obj
}
var aa = create(p);
类型判断
最简单的typeof
,但有时并不能准确的判断出数据的类型typeof [1,2]得到object
,列出我自己常用的一套方法吧:Object.prototype.toString.call(要检查的数据)
,根本原理是利用借用object
的prototype.toString
方法
// 以下是11种:
var number = 1; // [object Number]
var string = '123'; // [object String]
var boolean = true; // [object Boolean]
var und = undefined; // [object Undefined]
var nul = null; // [object Null]
var obj = {
a: 1
} // [object Object]
var array = [1, 2, 3]; // [object Array]
var date = new Date(); // [object Date]
var error = new Error(); // [object Error]
var reg = /a/g; // [object RegExp]
var func = function a() {}; // [object Function]
console.log(Object.prototype.toString.call(Math)); // [object Math]
console.log(Object.prototype.toString.call(JSON)); // [object JSON]
//查看参数的类型
//console.log(Object.prototype.toString.call(arguments)); // [object Arguments]
//封装一下
function checkType(obj){
return Object.prototype.toString.call(obj);
}
console.log(checkType([1, 2, 3])) // [object Array]
防抖与节流
我的另一篇文章有简述这些原理简单的聊聊节流和防抖
this指向问题
this
说简单点就是:谁调用了函数,this
就指向谁,在箭头函数中会稍有区别。
箭头函数中的this指向:箭头函数不绑定this, 它会捕获其所在(即定义的位置)上下文的this值, 作为自己的this值,看下面的栗子:
function Person() {
this.age = 111;
setTimeout(() => {
// 回调里面的 `this` 变量就指向了期望的那个对象了
console.log(this)
}, 1000);
}
Person() // Window 因为Person方法挂载到Window上了,上下文中的this也指向Window
var p = new Person() //Person {age: 111} 构造函数新生成的对象,上下文中的this指向p
修改this指向 call和apply和bind的区别
原方法fn(1,2)
,修改后fn
中的this指向了target
,修改this指向
的方法如下,三种方式作用相同,只是调用方式不同:
call: fn.call(target, 1, 2)
apply: fn.apply(target, [1, 2])
bind: fn.bind(target)(1,2)
function a(){
console.log(this.myName)
}
a() //undefined
var b = {
myName:"我是b"
}
a.call(b) // 我是b
a.apply(b) // 我是b
a.bind(b)() // 我是b
函数柯里化
在一个函数中,首先填充几个参数,然后再返回一个新的函数的技术,称为函数的柯里化。通常可用于在不侵入函数的前提下,为函数预置通用参数,供多次重复调用。
const curry = function add(x) {
return function (y) {
return x - y
}
}
// 通用参数1
const reduce = curry(1)
add1(2) === 1
add1(20) === 19
跨域问题
JavaScript 执行机制
从输入 url 到展示的过程
- DNS 解析
- TCP 三次握手
- 发送请求,分析 url,设置请求报文(头,主体),协商缓存问题
- 服务器返回请求的文件 (html)
- 浏览器渲染
1.HTML parser --> DOM Tree。html解析,dom数构建
2.CSS parser --> Style Tree。解析 css 代码,生成样式树
3.attachment --> Render Tree。结合 dom树 与 style树,生成渲染树
4.layout: 布局
5.GPU painting: 像素绘制页面
重绘和重排(回流)
重绘(repaint): 当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此 损耗较少
-
重排(回流)(reflow): 当元素的尺寸、结构或触发某些属性时,浏览器会重新渲染页面,称为重排(回流)。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。会触发重排(回流)的操作:
1.页面初次渲染
2.浏览器窗口大小改变
3.元素尺寸、位置、内容发生改变
4.元素字体大小变化
5.添加或者删除可见的 dom 元素
6.激活 CSS 伪类(例如::hover)
7.查询某些属性或者使用某些方法clientWidth、clientHeight、clientTop、clientLeft offsetWidth、offsetHeight、offsetTop、offsetLeft scrollWidth、scrollHeight、scrollTop、scrollLeft getComputedStyle() getBoundingClientRect() scrollTo()
重绘的开销较小,重排(回流)的开销较高。
一些优化的操作:
- 避免使用table布局,可能很小的一个小改动会造成整个 table 的重新布局
- 将CSS动画效果(transform)应用到position属性为absolute或fixed的元素上(定位元素修改不影响页面结构)
- JS中避免频繁操作样式,可汇总后统一 一次修改
- JS中尽量使用class进行样式修改,尽可能在DOM树的最末端改变class,回流是不可避免的,但可以减少其影响。尽可能在DOM树的最末端改变class,可以限制了回流的范围,使其影响尽可能少的节点。
- 减少dom的增删次数,可使用拼接好的字符串
append
或者documentFragment
一次性插入 - 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流
- 避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多。
前端存储
-
cookie
: 通常用于存储用户身份,登录状态等,http 中自动携带, 体积上限为 4K, 可自行设置过期时间 -
localStorage / sessionStorage
: 长久储存/窗口关闭删除, 体积限制为 4~5M indexDB
js内存问题
内存泄漏:不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。
V8垃圾回收机制:将内存中不再使用的数据进行清理,释放出内存空间。
常见内存泄漏的原因以及解决办法(让垃圾回收机制认为其不会再被使用):
1.全局变量,不会被回收。解决:用完需要手动赋值为null
2.定时器:没有被清理。解决:用完需要clear
2.闭包常驻内存引起
3.dom元素被清空或删除时,其绑定的事件未清除 被清空或删除的元素。解决:手动解绑事件removeEventListener
常见状态码
- 1xx: 接受,继续处理
- 200: 成功,并返回数据
- 201: 已创建请求
- 202: 服务端已接受请求
- 203: 成为,未授权
- 204: 成功,无内容
- 205: 成功,重置内容
- 206: 成功,部分内容
- 301: 永久移动,重定向
- 302: 临时移动,可使用原有URI
- 304: 资源未修改,可使用缓存
- 305: 需代理访问
- 400: 请求语法错误
- 401: 要求身份认证
- 403: 拒绝请求
- 404: 资源不存在
- 500: 服务器错误
get / post区别和相同
GET在浏览器回退时是无害的,而POST会再次提交请求。
GET产生的URL地址可以存书签,而POST不可以。
GET请求会被浏览器主动cache,而POST不会,除非手动设置。
GET请求只能进行url编码,而POST支持多种编码方式。
GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。因为参数直接暴露在URL上,所以不能用来传递敏感信息,POST比GET安全
GET请求在URL中传送的参数是有长度限制的,而POST么有。GET参数通过URL传递,POST放在Request body中
对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
GET产生一个TCP数据包;POST产生两个TCP数据包。
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。也就是说,GET只需要跑一趟就把货送到了,而POST得跑两趟,第一趟,先去和服务器打个招呼“嗨,我等下要送一批货来,你们打开门迎接我”,然后再回头把货送过去。在网速快时,多跑一趟消耗的时间可以忽略不计的。并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。GET/POST最底层都是TCP链接,底层相同
HTTP定义了好几个不同的请求方式名:GET, POST, PUT, DELETE等等都有其独特的意义。但HTTP只是个行为准则,而TCP才是GET和POST怎么实现的基本。就像校规规定了不能抽烟,但我依然可以偷偷的抽。要给GET加上request body,给POST带上url参数,技术上是完全行的通的。
前端安全问题
-
XSS攻击: 注入恶意代码
- cookie 设置 httpOnly
- 转义页面上的输入内容和输出内容 如
<>
转义为< >
-
CSRF: 跨站请求伪造,防护:
- get 不修改数据
- 不被第三方网站访问到用户的 cookie
- 设置白名单,不被第三方网站请求 如服务端CORS设置请求头
Access-Control-Allow-Origin: 域名白名单
- 请求校验,如
token
双向绑定之Proxy
目前,Vue 的反应系统是使用数据劫持 Object.defineProperty
的 getter
和 setter
。 但是,Vue 3 将使用 ES2015 Proxy
作为其观察者机制。两者的区别 。这消除了以前存在的警告,使速度加倍,并节省了一半的内存开销。理解Proxy
AMD和CMD的区别
- AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
- CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
关于vue或者react组件中需要写key的必要性
vue和react都是采用diff算法来对比新旧虚拟节点,从而更新节点。
<ul>
<li v-for="i in 10" :key="i">{{i}}</li>
</ul>
没有key:当其中一个li
发生变化时,会便利所有li
,找出其中变化的项
有key:会通过对应的key
值去映射,不用便利所有节
JS运算符优先级问题,以及函数声明优先级问题
function Foo () {
getName = function () { alert(1) }
return this
}
Foo.getName = function () { alert(2) }
Foo.prototype.getName = function () { alert(3) }
var getName = function () { alert(4) }
function getName () { alert(5) }
//输入的值
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
// 结果
Foo.getName(); //2
getName(); //4
Foo().getName(); //1
getName();//1
new Foo.getName(); //2
new Foo().getName(); //3
new new Foo().getName(); //3
上题主要测试了JS运算符优先级问题,以及函数声明优先级问题,参考文章:一道被前端忽略的基础题
var和let和const的区别
- var存在变量提升问题,可以重复声明
- const不能被修改,不能被重复声明
- let以下
// 同一作用域下,已经被声明的变量不能再被声明
let a = 0;
let a = 1; //Identifier 'a' has already been declared
// 暂时性死区问题,即不存在变量提升问题
b = 111;
let b; // b is not defined
// 经典面试题
for ( var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
// 使用let之后
for ( let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
// 相当于
{ // 形成块级作用域
let i = 0
{
let ii = i
setTimeout( function timer() {
console.log( ii );
}, i*1000 );
}
i++
{
let ii = i
}
i++
{
let ii = i
}
...
}
同步和异步的区别是什么
js单线程语言(不能同时干两件事),异步:定时器,ajax
以下代码,从上往下执行:
1.同步队列---打印1
2.发现定时器,把2加入到异步队列
3.同步队列---打印3
4.发现定时器,把3加入到异步队列,发现3等待的时间比2短,3插队,排在2之前
5.同步队列---打印5
6.同步任务全部执行完成,开始执行异步队列
7.按照队列 ----- 3 ----- 2
console.log(1)
setTimeout(function(){
console.log(2)
},1000)
console.log(3)
setTimeout(function(){
console.log(4)
},0)
console.log(5)
Date的方法
Date.now() //获取当前时间毫秒数
var dt = new Date()
dt.getTime() //获取毫秒数
dt.getFullYear() //年
dt.getMonth() //月(0-11)
dt.getDate() //日(0-31)
dt.getHours() //小时(0-23)
dt.getMinutes() //分钟(0-59)
dt.getSeconds() //秒(0-59)
获取2016-06-10格式的日期
function formatDate(dt){
if(!dt){
dt = new Date()
}
var year = dt.getFullYear()
var month = dt.getMonth() + 1
var date = dt.getDate()
if(month < 10){
//强制类型转换
month = '0' + month
}
if(date < 10){
//强制类型转换
date = '0' + date
}
//强制类型转换
return year + '-' + month + '-' + date
}
Array的方法
var arr = [1,2,3,4,5];
// forEach不会对空数组进行检测。
arr.forEach( function(item,index){
// 无法break
console.log(index,item);
});
// every不会对空数组进行检测。
// every不会改变原始数组。
var result = arr.every(function(index,item){
//所有成员都满足一个条件才为true
if(item<6)
return true
});
// some不会对空数组进行检测。
// some不会改变原始数组。
var result = arr.some(function(index,item){
//一个满足条件就为true
if(item<5)
return true;
});
console.log(result);
// 返回一个数组,原数组会被改变
var arr2 = arr.sort(function(a,b){
//从小到大排序 return a-b;
//从大到小排序 return b-a;
})
// map不会对空数组进行检测。
// map 不会改变原始数组。
arr.map(function(item,index){
// 每位数都X2
return item*2
});
// filter不会对空数组进行检测
// filter不会改变原始数组
var arr2 = arr.filter(function (item,index){
//通过某一个条件过滤数组,返回新数组
return item > 3
})
join: 通过指定连接符生成字符串
push / pop: 末尾推入和弹出,改变原数组, 返回推入/弹出项
unshift / shift: 头部推入和弹出,改变原数组,返回操作项
sort(fn) / reverse: 排序与反转,改变原数组
concat: 连接数组,不影响原数组, 浅拷贝
slice(start, end): 返回截断后的新数组,不改变原数组
splice(start, number, value...): 返回删除元素组成的数组,value 为插入项,改变原数组
indexOf / lastIndexOf(value, fromIndex): 查找数组项,返回对应的下标
事件代理
需求:点击li时,得到对应的数字
为每个li绑定事件(绑定多次事件)是没必要的,使用代理:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
document.querySelector('ul').onclick = function(event){
// 通过event.target得到对应的li
alert(event.target.innerTxt);
}
</script>
关于0.1 + 0.2 != 0.3的解决办法
parseFloat((0.1 + 0.2).toFixed(10))
关于['1','2','3'].map(parseInt)
console.log(['1','2','3'].map(parseInt)); // 1 NaN NaN
基于map方法,默认回传数组的(item,index,Arrar)。相当于执行了以下函数:
parseInt('1',0,['1','2','3'])
parseInt('2',1,['1','2','3'])
parseInt('3',2,['1','2','3'])
来看parsInt:
第一次,当我我们第一次调用的时候 是这样的:parseInt('1',0) 这个是没问题的 转十进制的 看我红框的图片返回 1
第二次,调用第二个index参数是1,也是说1作为数值的基础。规范里说的很清楚了,如果基础是非0或者小于2,函数都不会查询字符串直接返回NaN。
- 第三次,2作为基数。这就意味着字符串将被解析成字节数,也就是仅仅包含数值0和1。parseInt的规范第十一步指出,它仅尝试分析第一个字符的左侧,这个字符还不是要求基数的有效数字。这个字符串的第一个字符是“3”,它并不是基础基数2的一个有效数字。所以这个子字符串将被解析为空。函数将返回为NaN。
cookies与session有什么区别
由于http请求是无状态的,需要cookie来做身份验证
1.cookies由服务端创建,发送给浏览器端,当用户发出请求后,带上cookie发送给服务端做验证。
2.来回传递过程中,占用带宽,消耗网络资源,并且容易被中间人获取或浏览器端用户篡改十分不安全
3.cookies大小只有4k
1.session主要存在于服务端,不会被发送到浏览器所以很安全
2.如果没有设置过期时间,在会话结束后session会自动消失
3.session主要用于服务端保存请求信息的机制