基本面试:
做过最满意的项目是什么?项目背景?为什么要做这件事情?最终达到什么效果?你处于什么样的角色,起到了什么方面的作用?在项目中遇到什么技术问题?具体是如何解决的? 如果再做这个项目,你会在哪些方面进行改善?
基础扎实:
IT行业,哪些发展好的同学都是具备扎实基础知识,如果理解计算机基础会更好,因为我们面临很多非前端计算问题的。
主动思考:
被动完成任务成长会很慢的,需要有自己的想法,而不是仅仅是完成任务的。
自动学习:
前端领域知识淘汰速度很快,需要经常学习新知识。
追溯深度:
遇到问题是多研究背后深层次的原因,而不是绕过去。如遇到一个bug,有时间一定要理解本质原因。
宽阔视野:
创新往往来自不同领域的交集,如果理解更多领域,就会有更多的想法。
学习总结:
谈到某个技术的时候,需扪心自问,学这个是为了做什么?如何学习的?可以从什么渠道了解最新的知识?
告知浏览器解析器用什么文档标准解析这个文档
title,header,nav,main,article,h1~h6,ul,ol,address,canvas,dialog,aside,section,figure,details,mark
1 属性差别。link属于XHTML标签,而@import完全是CSS提供的语法规则。
2 加载顺序的差别。当一个页面被加载的时候(就是被浏览者浏览的时候),link引用的CSS会同时被加载,而@import引用的CSS会等到页面全部被下载完再被加载。所以有时候浏览@import加载CSS的页面时开始会没有样式(就是闪烁)
3 兼容性的差别。由于@import是CSS2.1提出的所以老的浏览器不支持,@import只有在IE5以上的才能识别,而link标签无此问题。
< header >定义了文档的头部区域
< footer >定义了文档的尾部区域
< nav >定义文档的导航
< section >定义文档中的节(section、区段)
< article >定义页面独立的内容区域
< aside >定义页面的侧边栏内容
< dialog >定义对话框,比如提示框
< datalist > 元素规定输入域的选项列表,使用 元素的 list 属性与 元素的 id 绑定
< keygen> 提供一种验证用户的可靠方法,标签规定用于表单的密钥对生成器字段。
< output > 用于不同类型的输出
(1)粗暴型,禁用缩放
(2)利用 FastClick,其原理是: 检测到 touchend 事件后,立刻出发模拟 click 事件,并且把浏览器 300 毫秒之后真正出 发的事件给阻断掉
史上最全的CSS经典面试题
水平居中
text-align: center; /* 行内元素 */
margin: 0 auto; /* 块级元素 */
position:absolute +left:50%+ transform:translateX(-50%)
display:flex + justify-content: center
垂直居中
设置line-height 等于height
position:absolute +top:50%+ transform:translateY(-50%)
display:flex + align-items: center
display:table+display:table-cell + vertical-align: middle;
注意点: 当translateX与translateY同时存在的话,后面的属性会盖住前面的属性,反正就只能有一个存在的!所以要改变一下写法:transform:translate(-50%,-50%);
单行文本
.xxx{
width: calc(100% - 60rpx);
overflow: hidden; /*超出部分隐藏*/
text-overflow: ellipsis; /*三点表示*/
white-space:nowrap; /*强制不换行*/
}
多行文本
.xxx{
display: -webkit-box; /*弹性伸缩盒子模型显示*/
word-break: break-all;
-webkit-box-orient: vertical; /*检索盒子模型的排列方式*/
-webkit-line-clamp: 4; /*需要显示的行数 */
overflow: hidden;
text-overflow: ellipsis;
}
-webkit-user-select: none;
-ms-user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
user-select: none;
行内元素:a、b、span、img、input、strong、select、label、em、button、textarea
块级元素:div、ul、li、dl、dt、dd、p、h1-h6、blockquote
空元素:即系没有内容的HTML元素,例如:br、meta、hr、link、input、img
/*css3的transform*/
height: 1px;
transform: scale(0.5);
div{
width:0px;
height:0px;
border-top:10px solid red;
border-right:10px solid transparent;
border-bottom:10px solid transparent;
border-left:10px solid transparent;
}
1、局部处理
2、全局处理
flex-direction: row; /*排列方向*/
flex-wrap: wrap; /*一条轴线排不下,如何换行*/
flex-flow: || ; /*flex-direction和flex-wrap的简写形式,默认值为row nowrap*/
justify-content: space-around; /*X轴上的对齐方式*/
align-items: center;/*Y轴上如何对齐*/
align-content: center; /*多根轴线的对齐方式一根轴线,该属性不起作用*/
align-self: center; /*允许单个项目有与其他项目不一样的对齐方式*/
浮动元素碰到包含它的边框或者浮动元素的边框停留。由于浮动元素不在文档流中,所以文档流的块框表现得就像浮动框不存在一样,浮动元素会漂浮在文档流的块框上.
(1)父级div定义height。
(2)结尾处加空div标签clear:both。
(3)父级div定义伪类:after和zoom。
(4)父级div定义overflow:hidden。
(5)父级div定义overflow:auto。
(6)父级div也浮动,需要定义宽度。
(7)父级div定义display:table。
(8)结尾处加br标签clear:both。
(Q2)比较好的是第3种方式,好多网站都这么用
父级添加overflow属性(父元素添加overflow:hidden)
.fahter{
width: 400px;
border: 1px solid deeppink;
overflow: hidden;
}
使用after伪元素清除浮动(推荐使用)
.clearfix:after{/*伪元素是行内元素 正常浏览器清除浮动方法*/
content: "";
display: block;
height: 0;
clear:both;
visibility: hidden;
}
.clearfix{
*zoom: 1;/*ie6清除浮动的方式 *号只有IE6-IE7执行,其他浏览器不执行*/
}
"fahter clearfix">
"big">big
"small">small
"footer">
优点:符合闭合浮动思想,结构语义化正确
缺点:ie6-7不支持伪元素:after,使用zoom:1触发hasLayout.
使用before和after双伪元素清除浮动
.clearfix:after,.clearfix:before{
content: "";
display: table;
}
.clearfix:after{
clear: both;
}
.clearfix{
*zoom: 1;
}
"fahter clearfix">
"big">big
"small">small
"footer">
优点:代码更简洁
缺点:用zoom:1触发hasLayout.
注: haslayout是IE7-浏览器的特有属性。hasLayout是一种只读属性,有两种状态:true或false。当其为true时,代表该元素有自己的布局,否则代表该元素的布局继承于父元素。
.css {
-webkit-transform-style: preserve-3d;
-webkit-backface-visibility: hidden;
-webkit-perspective: 1000;
}
过渡动画(在没有启动硬件加速的情况下)会出现抖动的现象, 以上的解决方案只是改变视角来启动硬件加速的一种方式;启动硬件加速的另外一种方式:
.css {
-webkit-transform: translate3d(0,0,0);
-moz-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
启动硬件加速
最常用的方式:translate3d、translateZ、transform
opacity 属性/过渡动画(需要动画执行的过程中才会创建合成层,动画没有开始或结束后元素还会回到之前的状态)
will-chang 属性(这个比较偏僻),一般配合opacity与translate使用(而且经测试,除了上述可以引发硬件加速的属性外,其它属性并不会变成复合层)。
弊端:硬件加速会导致 CPU 性能占用量过大,电池电量消耗加大 ;因此尽量避免泛滥使用硬件加速。
@font-face {
font-family: 'MyFont'; /* 表示为这种字体起一个名称,可以随意设置,这里用的是MyFont */
src: url('myfont.eot'); /* 这一行表示字体位置,由于ie只支持服务器端的eot字体,所以这一行是ie专用的 */
src: local('myfont.ttf'),
url('myfont.woff') format('woff'),
url('myfont.ttf') format('truetype'); /* local()表示在本机(客户端)查找该字体,如果本机已经安装了,就不用下载了。url()表示字体在服务器上的位置,format()用来说明字体格式。Firefox 3.5支持TrueType和OpenType字体,Firefox 3.6又增加了WOFF字体。其他基于Webkit引擎的浏览器(sarif,opera、chrome),目前好像只支持truetype */
}
h2{ font-family: "MyFont"; } /*使用*/
CSS 预处理器为 CSS 增加一些编程的特性,无需考虑浏览器的兼容性问题”,例如你可以在 CSS 中使用变量、简单的逻辑程序、函数(如右侧代码编辑器中就使用了变量$color)等等在编程语言中的一些基本特性,可以让你的 CSS 更加简洁、适应性更强、可读性更佳,更易于代码的维护等诸多好处。
结构清晰,便于扩展。可以方便地屏蔽浏览器私有语法差异。封装对浏览器语法差异的重复处理,减少无意义的机械劳动。可以轻松实现多重继承。完全兼容 CSS 代码,可以方便地应用到老项目中,LESS 只是在 CSS 语法上做了扩展,所以老的 CSS 代码也可以与 LESS 代码一同编译。
预处理器例如:LESS、Sass、Stylus,用来预编译Sass或less,增强了css代码的复用性,还有层级、mixin、变量、循环、函数等,具有很方便的UI组件模块化开发能力,极大的提高工作效率。
后处理器例如:PostCSS,通常被视为在完成的样式表中根据CSS规范处理CSS,让其更有效;目前最常做的是给CSS属性添加浏览器私有前缀,实现跨浏览器兼容性的问题。
<!-- 设置缩放 -->
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, minimal-ui" />
<!-- 可隐藏地址栏,仅针对IOS的Safari(注:IOS7.0版本以后,safari上已看不到效果) -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- 仅针对IOS的Safari顶端状态条的样式(可选default/black/black-translucent ) -->
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<!-- IOS中禁用将数字识别为电话号码/忽略Android平台中对邮箱地址的识别 -->
<meta name="format-detection"content="telephone=no, email=no" />
<!-- 启用360浏览器的极速模式(webkit) -->
<meta name="renderer" content="webkit">
<!-- 避免IE使用兼容模式 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 针对手持设备优化,主要是针对一些老的不识别viewport的浏览器,比如黑莓 -->
<meta name="HandheldFriendly" content="true">
<!-- 微软的老式浏览器 -->
<meta name="MobileOptimized" content="320">
<!-- uc强制竖屏 -->
<meta name="screen-orientation" content="portrait">
<!-- QQ强制竖屏 -->
<meta name="x5-orientation" content="portrait">
<!-- UC强制全屏 -->
<meta name="full-screen" content="yes">
<!-- QQ强制全屏 -->
<meta name="x5-fullscreen" content="true">
<!-- UC应用模式 -->
<meta name="browsermode" content="application">
<!-- QQ应用模式 -->
<meta name="x5-page-mode" content="app">
<!-- windows phone 点击无高光 -->
<meta name="msapplication-tap-highlight" content="no">
在css中非中文文本是不会自动换行的,可以使用word-wrap: break-word;或word-break:break-all;来让英文强制换行。
word-break: break-all; 设置文字的强制自动换行,但只对英文起作用,以字母作为换行依据。
数据类型到一些隐式转换这些基础知识,看代码说输出,v8底层执行机制、垃圾回收、闭包、作用域、作用域链,原型、原型链,手写代码,如:防抖、节流、bind、call、apply、深拷贝、浅拷贝、Promise、async、await、webpack、框架、http等。
JavaScript是一种动态、弱类型、基于原型的语言,通过浏览器可以直接执行,当浏览器遇到< script > 标记的时候,浏览器会执行之间的javascript代码。嵌入的js代码是顺序执行的,每个脚本定义的全局变量和函数,都可以被后面执行的脚本所调用。 变量的调用,必须是前面已经声明,否则获取的变量值是undefined。
基本数据类型:string、number、null、undefined、boolean
引用数据类型:Object、Array、Date、RegExp
变量:var关键字来定义变量(ES5)、let命令来声明变量(ES6)、const命令声明一个只读的常量(ES6)
递归我们经常用到,vue在实现双向绑定进行数据检验的时候用的也是递归,但要我们面试的时候手写一个递归,如果对递归的概念理解不透彻,可能还是会有一些问题。
function add(num1,num2){
var num = num1+num2;
if(num2+1>100){
return num;
}else{
return add(num,num2+1)
}
}
var sum =add(1,2);
此题看着简单,但要想面试官给你高分还是有难度的。至少也要写出几种方法
js:
var arr=['12','32','89','12','12','78','12','32'];
// 最简单数组去重法
function unique1(array){
var n = []; //一个新的临时数组
for(var i = 0; i < array.length; i++){ //遍历当前数组
if (n.indexOf(array[i]) == -1)
n.push(array[i]);
}
return n;
}
arr=unique1(arr);
// 速度最快, 占空间最多(空间换时间)
function unique2(array){
var n = {}, r = [], type;
for (var i = 0; i < array.length; i++) {
type = typeof array[i];
if (!n[array[i]]) {
n[array[i]] = [type];
r.push(array[i]);
} else if (n[array[i]].indexOf(type) < 0) {
n[array[i]].push(type);
r.push(array[i]);
}
}
return r;
}
//数组下标判断法
function unique3(array){
var n = [array[0]]; //结果数组
for(var i = 1; i < array.length; i++) { //从第二项开始遍历
if (array.indexOf(array[i]) == i)
n.push(array[i]);
}
return n;
}
es6:
//es6方法一数组去重
arr=[...new Set(arr)];
//es6方法二数组去重,
function dedupe(array) {
return Array.from(new Set(array)); //Array.from()能把set结构转换为数组
}
Function
函数就是对象,代表函数的对象就是函数对象。所有的函数对象是被Function这个函数对象构造出来的。Function是最顶层的构造器。它构造了系统中所有的对象,包括用户自定义对象,系统内置对象,甚至包括它自已。这也表明Function具有自举性(自已构造自己的能力)。这也间接决定了Function的call和constructor逻辑相同。每个对象都有一个constructor 属性,用于指向创建其的函数对象。
a、函数与对象具有相同的语言地位
b、没有类,只有对象
c、函数也是一种对象,所谓的函数对象
d、对象是按引用来传递的
Object
对于Object它是最顶层的对象,所有的对象都将继承Object的原型,但是你也要明确的知道Object也是一个函数对象,所以说Object是被Function构造出来的。
//定义角度,前者为定义一个js函数,后者为这个函数的名称
function Function(){}
//用法角度,a也是function
var a = new Object(function(){});
会打印出false,这里会将true转变成1
var a=[];
a.constructor===Array //true
a instanceof Array === true //true
⚠️ 注意:以上方法在跨frame时会有问题,跨frame实例化的对象不共享原型
var iframe = document.createElement('iframe'); //创建iframe
document.body.appendChild(iframe); //添加到body中
xArray = window.frames[window.frames.length-1].Array;
var arr = new xArray(1,2,3); // 声明数组[1,2,3]
alert(arr instanceof Array); // false
alert(arr.constructor === Array); // false
解决:
Object.prototype.toString.call(a) // "[object Array]"
Array.isArray(a) //true
function foo() {
console.log(this.a)
}
var a = 1
foo()
var obj = {
a: 2,
foo: foo
}
obj.foo()
// 以上两者情况 `this` 只依赖于调用函数前的对象,优先级是第二个情况大于第一个情况
// 以下情况是优先级最高的,`this` 只会绑定在 `c` 上,不会被任何方式修改 `this` 指向
var c = new foo()
c.a = 3
console.log(c.a)
// 还有种就是利用 call,apply,bind 改变 this,这个优先级仅次于 new
function a() {
return () => {
return () => {
console.log(this)
}
}
}
console.log(a()()())
箭头函数其实是没有 this 的,这个函数中的 this 只取决于他外面的第一个不是箭头函数的函数的 this。在这个例子中,因为调用 a 符合前面代码中的第一个情况,所以 this 是 window。并且 this 一旦绑定了上下文,就不会被任何代码改变。
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
具体在业务中使用哪个,还得根据自己的业务需求。当然,深拷贝的方法也有很多个,这里只列出常用的一种方法,JSON.parse(JSON.stringify())
浅拷贝:定义了一个obj对象,和一个newObj对象,并让newObj的person等于指向obj对象(实则是将newObj的person属性指向obj对象的指针),所以改变newObj.person.name的值,实则是改变obj.name的值。
var obj = {
id: 123,
name: "小三",
address: "china"
}
var newObj = {}
newObj.person = obj; //浅拷贝
newObj.person.name = "小三"
console.log(obj.name); //打印小三
深拷贝 :定义obj对象和newObj对象,在给newObje对象的person属性赋值的时候,我们用JSON.parse(JSON.stringify(obj)) 方法将obj深拷贝了一份,也就是说重新申请了一个空间,与原对象不共享内存,属于完全独立的存在,所以在改变newObj.person.name属性之后,obj.name是不会跟着发生改变的。
var obj = {
id:123,
name:"小三",
address:"china"
}
var newObj = {}
newObj.person = JSON.parse(JSON.stringify(obj)); //深拷贝
newObj.person.name="小三"
console.log(obj.name);// 打印小三
Promise 是 ES6 新增的语法,解决了回调地狱的问题。
可以把 Promise 看成一个状态机。初始是 pending 状态,可以通过函数 resolve 和 reject ,将状态转变为 resolved 或者 rejected 状态,状态一旦改变就不能再次变化。
then 函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise 规范规定除了 pending 状态,其他状态是不可以改变的,如果返回的是一个相同实例的话,多个 then 调用就失去意义了。
对于 then 来说,本质上可以把它看成是 flatMap
// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
// promise 接收一个函数参数,该函数会立即执行
function MyPromise(fn) {
let _this = this;
_this.currentState = PENDING;
_this.value = undefined;
// 用于保存 then 中的回调,只有当 promise
// 状态为 pending 时才会缓存,并且每个实例至多缓存一个
_this.resolvedCallbacks = [];
_this.rejectedCallbacks = [];
_this.resolve = function (value) {
if (value instanceof MyPromise) {
// 如果 value 是个 Promise,递归执行
return value.then(_this.resolve, _this.reject)
}
setTimeout(() => { // 异步执行,保证执行顺序
if (_this.currentState === PENDING) {
_this.currentState = RESOLVED;
_this.value = value;
_this.resolvedCallbacks.forEach(cb => cb());
}
})
};
_this.reject = function (reason) {
setTimeout(() => { // 异步执行,保证执行顺序
if (_this.currentState === PENDING) {
_this.currentState = REJECTED;
_this.value = reason;
_this.rejectedCallbacks.forEach(cb => cb());
}
})
}
// 用于解决以下问题
// new Promise(() => throw Error('error))
try {
fn(_this.resolve, _this.reject);
} catch (e) {
_this.reject(e);
}
}
MyPromise.prototype.then = function (onResolved, onRejected) {
var self = this;
// 规范 2.2.7,then 必须返回一个新的 promise
var promise2;
// 规范 2.2.onResolved 和 onRejected 都为可选参数
// 如果类型不是函数需要忽略,同时也实现了透传
// Promise.resolve(4).then().then((value) => console.log(value))
onResolved = typeof onResolved === 'function' ? onResolved : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : r => throw r;
if (self.currentState === RESOLVED) {
return (promise2 = new MyPromise(function (resolve, reject) {
// 规范 2.2.4,保证 onFulfilled,onRjected 异步执行
// 所以用了 setTimeout 包裹下
setTimeout(function () {
try {
var x = onResolved(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
});
}));
}
if (self.currentState === REJECTED) {
return (promise2 = new MyPromise(function (resolve, reject) {
setTimeout(function () {
// 异步执行onRejected
try {
var x = onRejected(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
});
}));
if (self.currentState === PENDING) {
return (promise2 = new MyPromise(function (resolve, reject) {
self.resolvedCallbacks.push(function () {
// 考虑到可能会有报错,所以使用 try/catch 包裹
try {
var x = onResolved(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
self.rejectedCallbacks.push(function () {
try {
var x = onRejected(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
}));
}
};
// 规范 2.3
function resolutionProcedure(promise2, x, resolve, reject) {
// 规范 2.3.1,x 不能和 promise2 相同,避免循环引用
if (promise2 === x) {
return reject(new TypeError("Error"));
}
// 规范 2.3.2
// 如果 x 为 Promise,状态为 pending 需要继续等待否则执行
if (x instanceof MyPromise) {
if (x.currentState === PENDING) {
x.then(function (value) {
// 再次调用该函数是为了确认 x resolve 的
// 参数是什么类型,如果是基本类型就再次 resolve
// 把值传给下个 then
resolutionProcedure(promise2, value, resolve, reject);
}, reject);
} else {
x.then(resolve, reject);
}
return;
}
// 规范 2.3.3.3.3
// reject 或者 resolve 其中一个执行过得话,忽略其他的
let called = false;
// 规范 2.3.3,判断 x 是否为对象或者函数
if (x !== null && (typeof x === "object" || typeof x === "function")) {
// 规范 2.3.3.2,如果不能取出 then,就 reject
try {
// 规范 2.3.3.1
let then = x.then;
// 如果 then 是函数,调用 x.then
if (typeof then === "function") {
// 规范 2.3.3.3
then.call(
x,
y => {
if (called) return;
called = true;
// 规范 2.3.3.3.1
resolutionProcedure(promise2, y, resolve, reject);
},
e => {
if (called) return;
called = true;
reject(e);
}
);
} else {
// 规范 2.3.3.4
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
// 规范 2.3.4,x 为基本类型
resolve(x);
}
}
以上就是根据 Promise / A+ 规范来实现的代码,可以通过 promises-aplus-tests 的完整测试
var 声明的变量会挂载在 window 上,而 let 和 const 声明的变量不会:var 声明变量存在变量提升,let 和 const 不存在变量提升let 和 const 声明形成块作用域同一作用域下 let 和 const 不能声明同名变量,而 var 可以。
Const 1、一旦声明必须赋值,不能使用 null 占位。2、声明后不能再修改 3、如果声明的是复合类型数据,可以修改其属性
必要性: 由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript 程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript 的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。
这段话解释了为什么需要系统需要垃圾回收,JS 不像 C/C++,他有自己的一套垃圾回收机制(Garbage Collection),avaScript 的解释器可以检测到何时程序不再使用一个对象了,当他确定了一个对象是无用的时候,他就知道不再需要这个对象,可以把它所占用的内存释放掉了。例如:
var a="hello world";
var b="world";
var a=b;
//这时,会释放掉"hello world"
//释放内存以便再引用 垃圾回收的方法:标记清除、计数引用。
标记清除:
这是最常见的垃圾回收方式,当变量进入环境时,就标记这个变量为”进入环境“,从逻 辑上讲,永远不能释放进入环境的变量所占的内存,永远不能释放进入环境变量所占用 的内存,只要执行流程进入相应的环境,就可能用到他们。当离开环境时,就标记为离开环境。 垃圾回收器在运行的时候会给存储在内存中的变量都加上标记(所有都加),然后去掉 环境变量中的变量,以及被环境变量中的变量所引用的变量(条件性去除标记),删除 所有被标记的变量,删除的变量无法在环境变量中被访问所以会被删除,最后垃圾回收器,完成了内存的清除工作,并回收他们所占用的内存。
引用计数法:
另一种不太常见的方法就是引用计数法,引用计数法的意思就是每个值没引用的次数, 当声明了一个变量,并用一个引用类型的值赋值给改变量,则这个值的引用次数为 1; 相反的,如果包含了对这个值引用的变量又取得了另外一个值,则原先的引用值引用次 数就减 1,当这个值的引用次数为 0 的时候,说明没有办法再访问这个值了,因此就把 所占的内存给回收进来,这样垃圾收集器再次运行的时候,就会释放引用次数为 0 的这 些值。 用引用计数法会存在内存泄露,下面来看原因:
function problem() {
var objA = new Object();
var objB = new Object();
objA.someOtherObject = objB;
objB.anotherObject = objA;
}
在这个例子里面,objA 和 objB 通过各自的属性相互引用,这样的话,两个对象的引用 次数都为 2,在采用引用计数的策略中,由于函数执行之后,这两个对象都离开了作用 域,函数执行完成之后,因为计数不为 0,这样的相互引用如果大量存在就会导致内存 泄露。
特别是在 DOM 对象中,也容易存在这种问题:
DOM 的变化影响到了预算内宿的几何属性比如宽高,浏览器重新计算元素的几何属性, 其他元素的几何属性也会受到影响,浏览器需要重新构造渲染书,这个过程称之为重排,
浏览器将受到影响的部分重新绘制在屏幕上 的过程称为重绘,
var element=document.getElementById(’‘);
var myObj=new Object();
myObj.element=element;
element.someObject=myObj;
这样就不会有垃圾回收的过程。
引起重排重绘的原因有:
减少重绘重排的方法有:
不在布局信息改变时做 DOM 查询,
使用 csstext,className 一次性改变属性
使用 fragment
对于多次重排的元素,比如说动画。使用绝对定位脱离文档流,使其不影响其他元素
new 操作符新建了一个空对象,这个对象原型指向构造函数的 prototype,执行构造函数 后返回这个对象。
通过 apply 和 call 改变函数的 this 指向,他们两个函数的第一个参数都是一样的表示要 改变指向的那个对象,第二个参数,apply 是数组,而 call 则是 arg1,arg2…这种形式。通 过 bind 改变 this 作用域会返回一个新的函数,这个函数不会马上执行。
在 ajax 发送请求前加上 anyAjaxObj.setRequestHeader(“If-Modified-Since”,“0”)。
在 ajax 发送请求前加上
anyAjaxObj.setRequestHeader(“Cache-Control”,“no-cache”)。
在 URL 后面加上一个随机数: “fresh=” + Math.random()。
在 URL 后面加上时间搓:“nowtime=” + new Date().getTime()。
如果是使用 jQuery,直接这样就可以了 $.ajaxSetup({cache:false})。这样页面的所有 ajax
都会执行这条语句就是不需要保存缓存记录。
指定 script 标签的 async 属性。 如果 async=“async”,脚本相对于页面的其余部分异步地执行(当页面继续进行解析时, 脚本将被执行) 如果不使用 async 且 defer=“defer”:脚本将在页面完成解析时执行
面向对象的基本特征有:封闭、继承、多态。
function car(price){
this.price = price;
}
car.prototype.sayPrice = function(){
console.log("Price is "+this.price);
}
var oCar = new car("100W");
oCar.sayPrice();
function toyCar(price){
this.price = price;
}
toyCar.prototype = new car()
var oCar2 = new toyCar("10CNY");
oCar2.sayPrice();
function useCall(a,b){
this.a = a;
this.b = b;
this.say = function(){
console.log("I'm "+this.a+" You're "+this.b);
}
}
function callThefunction (){
var args = arguments;
useCall.call(this,args[0],args[1]);
// useCall.apply(this,arguments);
}
var testCall1 = new useCall("Not YY","Not TT");
testCall1.say();
var testCall2 = new callThefunction("YY","TT");
testCall2.say();
function house(size,price){
this.size = size;
this.price = price;
}
house.prototype.showArea=function (){
console.log("面积为"+this.size);
}
house.prototype.sayPrice=function (){
console.log("价钱为"+this.price);
}
function maofan(size,price){
house.call(this,size,price);
}
maofan.prototype = new house();
var newmaofan = new maofan("20Square meters ","1000CNY");
newmaofan.showArea();
function Person(name,age){
this.name = name;
this.age = age;
this.show = function(){
console.log(this.name+", "+this.age);
}
}
Person.prototype.sayHi = function(){
alert('hi');
}
function Student(name,age){
this.student = Person; //将Person类的构造函数赋值给this.student
this.student(name,age); //js中实际上是通过对象冒充来实现继承的
delete this.student; //移除对Person的引用
}
var s = new Student("小明",17);
s.show();
var p = new Person("小花",18);
p.show();
// 小明, 17
// 小花, 18
一种是设置超时时间让ajax自动断开,另一种是手动停止ajax请求,其核心是调用XML对象的abort方法,ajax.abort()
中断连接端可以是Client端,也可以是Server端。
1xx(临时响应)
100: 请求者应当继续提出请求。
101(切换协议) 请求者已要求服务器切换协议,服务器已确认并准备进行切换。
2xx(成功)
200:正确的请求返回正确的结果
201:表示资源被正确的创建。比如说,我们 POST 用户名、密码正确创建了一个用户就可以返回 201。
202:请求是正确的,但是结果正在处理中,这时候客户端可以通过轮询等机制继续请求。
3xx(已重定向)
300:请求成功,但结果有多种选择。
301:请求成功,但是资源被永久转移。
303:使用 GET 来访问新的地址来获取资源。
304:请求的资源并没有被修改过
4xx(请求错误)
400:请求出现错误,比如请求头不对等。
401:没有提供认证信息。请求的时候没有带上 Token 等。
402:为以后需要所保留的状态码。
403:请求的资源不允许访问。就是说没有权限。
404:请求的内容不存在。
5xx(服务器错误)
500:服务器错误。
501:请求还没有被实现。
做移动端的时候,设计师图片上的文字假如是10px,我们实现在网页上之后。往往设计师回来找我们,这个字体能小一些吗?我设计的是10px?为啥是12px?其实我们都知道,谷歌Chrome最小字体是12px,不管你设置成8px还是10px,在浏览器中只会显示12px,那么如何解决这个坑爹的问题呢?
针对谷歌浏览器内核,加webkit前缀,用**transform:scale()**这个属性进行缩放!
小可爱10px
构造两棵树,DOM 树和 CSSOM 规则树,
当浏览器接收到服务器相应来的 HTML 文档后,会遍历文档节点,生成 DOM 树, CSSOM 规则树由浏览器解析 CSS 文件生成。
前端路由实现起来其实很简单,本质就是监听 URL 的变化,然后匹配路由规则,显示相应的页面,并且无须刷新。目前单页面使用的路由就只有两种实现方式
www.test.com/##/ 就是 Hash URL,当 ## 后面的哈希值发生变化时,不会向服务器请求数据,可以通过 hashchange 事件来监听到 URL 的变化,从而进行跳转页面。
History 模式是 HTML5 新推出的功能,比之 Hash URL 更加美观
当触发了指定事件后会进入脏数据检测,这时会调用 $digest 循环遍历所有的数据观察者,判断当前值是否和先前的值有区别,如果检测到变化的话,会调用 $watch 函数,然后再次调用 $digest 循环直到发现没有变化。循环至少为二次 ,至多为十次。
脏数据检测虽然存在低效的问题,但是不关心数据是通过什么方式改变的,都可以完成任务,但是这在 Vue 中的双向绑定是存在问题的。并且脏数据检测可以实现批量检测出更新的值,再去统一更新 UI,大大减少了操作 DOM 的次数。所以低效也是相对的,这就仁者见仁智者见智了。
项目中前端需要统一处理后端返回的状态码并给出弹窗提示,需要在全局环境下对axios设置拦截器。
类似于401、403、500等状态码都可以在error回调中捕获到,但是302状态码是捕获不到的,因为当状态时302时,浏览器自行根据redirectUrl进行了跳转,所以无法在success回调中捕获弹窗,前端是无能为力的。
解决办法:
axios.interceptors.response.use((response) => {
return response;
}, function (error) {
if (401 === error.response.status) {
window.location = '/login';
} else {
return Promise.reject(error);
}
});
注意: axios请求头部不自带头部‘X-Requested-With’的,所以要加一句默认为异步请求:
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
a. diff 方法优化
Vue2.x 中的虚拟 dom 是进行全量的对比。
Vue3.0 中新增了静态标记(PatchFlag):在与上次虚拟结点进行对比的时候,值对比 带有 patch flag 的节点,并且可以通过 flag 的信息得知当前节点要对比的具体内容化。
b. hoistStatic 静态提升:
Vue2.x : 无论元素是否参与更新,每次都会重新创建。
Vue3.0 : 对不参与更新的元素,只会被创建一次,之后会在每次渲染时候被不停的复用。
c. cacheHandlers 事件侦听器缓存:
默认情况下 onClick 会被视为动态绑定,所以每次都会去追踪它的变化但是因为是同一 个函数,所以没有追踪变化,直接缓存起来复用即可。
ajax是一种通过后台与服务器进行少量的数据交换,使页面实现异步更新是一种创建交互式网页应用的网页开发技术。
1、创建ajax对象(XMLHttpRequest/ActiveXObject(Microsoft.XMLHttp))
2、判断数据传输方式(GET/POST)
3、打开链接 open()
4、发送 send()
5、当ajax对象完成第四步(onreadystatechange)数据接收完成,判断http响应状态(status)200-300之间或者304(缓存)执行回调函数
异步请求响应快,用户体验好;页面无刷新、数据局部更新;按需取数据,减少了冗余请求和服务器的负担。
异步回调问题、this指向问题、路由跳转back问题;对搜索引擎的支持比较弱,对于一些手机还不是很好的支持
DNS 解析也是需要时间的,可以通过预解析的方式来预先获得域名所对应的 IP。
<link rel="dns-prefetch" href="//yuchengkai.cn" />
缓存对于前端性能优化来说是个很重要的点,良好的缓存策略可以降低资源的重复加载提高网页的整体加载速度。
通常浏览器缓存策略分为两种:强缓存和协商缓存。
实现强缓存可以通过两种响应头实现:Expires 和 Cache-Control 。强缓存表示在缓存期间不需要请求,state code 为 200。
Expires: Wed, 22 Oct 2018 08:41:00 GMT
Cache-control: max-age=30
如果缓存过期了,我们就可以使用协商缓存来解决问题。协商缓存需要请求,如果缓存有效会返回 304。
协商缓存需要客户端和服务端共同实现,和强缓存一样,也有两种实现方式。
Last-Modified 和 If-Modified-Since:
Last-Modified 表示本地文件最后修改日期,If-Modified-Since 会将 Last-Modified 的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来。
但是如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现了 ETag 。
ETag 和 If-None-Match:
ETag 类似于文件指纹,If-None-Match 会将当前 ETag 发送给服务器,询问该资源 ETag 是否变动,有变动的话就将新的资源发送回来。并且 ETag 优先级比 Last-Modified 高。
在开发中,可能会遇到这样的情况。有些资源不需要马上用到,但是希望尽早获取,这时候就可以使用预加载。
预加载其实是声明式的 fetch ,强制浏览器请求资源,并且不会阻塞 onload 事件,可以使用以下代码开启预加载.
<link rel="preload" href="http://example.com" />
预加载可以一定程度上降低首屏的加载时间,因为可以将一些不影响首屏但重要的文件延后加载,唯一缺点就是兼容性不好。
<link rel="prerender" href="http://example.com" />
预渲染虽然可以提高页面的加载速度,但是要确保该页面百分百会被用户在之后打开,否则就白白浪费资源去渲染
日常开发中遇到滚动事件中需要做个复杂计算或者实现一个按钮的防二次点击操作。防抖和节流的作用都是防止函数多次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的情况下只会调用一次,而节流的 情况会每隔一定时间(参数wait)调用函数。
// 这个是用来获取当前时间戳的
function now() {
return +new Date()
}
/**
* 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
*
* @param {function} func 回调函数
* @param {number} wait 表示时间窗口的间隔
* @param {boolean} immediate 设置为ture时,是否立即调用函数
* @return {function} 返回客户调用函数
*/
function debounce (func, wait = 50, immediate = true) {
let timer, context, args
// 延迟执行函数
const later = () => setTimeout(() => {
// 延迟函数执行完毕,清空缓存的定时器序号
timer = null
// 延迟执行的情况下,函数会在延迟函数中执行
// 使用到之前缓存的参数和上下文
if (!immediate) {
func.apply(context, args)
context = args = null
}
}, wait)
// 这里返回的函数是每次实际调用的函数
return function(...params) {
// 如果没有创建延迟执行函数(later),就创建一个
if (!timer) {
timer = later()
// 如果是立即执行,调用函数
// 否则缓存参数和调用上下文
if (immediate) {
func.apply(this, params)
} else {
context = this
args = params
}
// 如果已有延迟执行函数(later),调用的时候清除原来的并重新设定一个
// 这样做延迟函数会重新计时
} else {
clearTimeout(timer)
timer = later()
}
}
}
对于按钮防点击来说的实现:如果函数是立即执行的,就立即调用,如果函数是延迟执行的,就缓存上下文和参数,放到延迟函数中去执行。一旦我开始一个定时器,只要我定时器还在,你每次点击我都重新计时。一旦你点累了,定时器时间到,定时器重置为 null,就可以再次点击了。对于延时执行函数来说的实现:清除定时器ID,如果是延迟调用就调用函数。
防抖动和节流本质是不一样的。防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。
/**
* underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait
*
* @param {function} func 回调函数
* @param {number} wait 表示时间窗口的间隔
* @param {object} options 如果想忽略开始函数的的调用,传入{leading: false}。
* 如果想忽略结尾函数的调用,传入{trailing: false}
* 两者不能共存,否则函数不能执行
* @return {function} 返回客户调用函数
*/
_.throttle = function(func, wait, options) {
var context, args, result;
var timeout = null;
// 之前的时间戳
var previous = 0;
// 如果 options 没传则设为空对象
if (!options) options = {};
// 定时器回调函数
var later = function() {
// 如果设置了 leading,就将 previous 设为 0
// 用于下面函数的第一个 if 判断
previous = options.leading === false ? 0 : _.now();
// 置空一是为了防止内存泄漏,二是为了下面的定时器判断
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function() {
// 获得当前时间戳
var now = _.now();
// 首次进入前者肯定为 true
// 如果需要第一次不执行函数
// 就将上次时间戳设为当前的
// 这样在接下来计算 remaining 的值时会大于0
if (!previous && options.leading === false) previous = now;
// 计算剩余时间
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 如果当前调用已经大于上次调用时间 + wait
// 或者用户手动调了时间
// 如果设置了 trailing,只会进入这个条件
// 如果没有设置 leading,那么第一次会进入这个条件
// 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了
// 其实还是会进入的,因为定时器的延时
// 并不是准确的时间,很可能你设置了2秒
// 但是他需要2.2秒才触发,这时候就会进入这个条件
if (remaining <= 0 || remaining > wait) {
// 如果存在定时器就清理掉否则会调用二次回调
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
// 判断是否设置了定时器和 trailing
// 没有的话就开启一个定时器
// 并且不能不能同时设置 leading 和 trailing
timeout = setTimeout(later, remaining);
}
return result;
};
};
每个页面至少在初始化的时候会有一次重排操作,任何对渲染树的修改,都可能引发重排或者重绘。
重绘(repaint):
当盒子的位置、大小以及其他属性,浏览器便把这些都按照各自的特性绘制一遍,将内容呈现在页面中。重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。
重排:
当渲染树中一部分,因为元素的规模尺寸,布局,隐藏等改变而需要重新构建,这就称为回流,每个页面至少需要一次回流,就是在页面第一次加载的时候。
重排和重绘的关系:在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分,该过程称为重绘,因此重排必定会引发重绘,但重绘不一定会引发重排。
如何减少重绘和重排
1、不要一个地单独修改属性,最好通过ClassName来定义这些修改
2、批量操作
(1)、把对节点的大量操作放在页面之外,用documentFragment来做修改。
(2)、clone节点,在clone之后的节点中做修改,然后直接替换掉以前的节点。
var fragment = document.createDocumentFragment();
var li = document.createElement('li');
li.innerHTML = 'apple';
fragment.appendChild(li);
var li = document.createElement('li');
li.innerHTML = 'watermelon';
fragment.appendChild(li);
document.getElementById('fruit').appendChild(fragment);
(3)、通过display:none来隐藏节点(直接导致一次重排和重绘),做大量的修改,然后显示节点(又一次重排和重绘),总共只有两次重排。
3、考虑到渲染树,一次修改会导致大量的绘制操作,比如绝对定位元素的动画就不会影响其他大部分元素。
4、浏览器优化: 浏览器自己会维护1个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理,这样就会让多次的回流、重绘变成一次回流重绘。
不要经常访问浏览器的flush队列属性;如果一定要访问,可以利用缓存,将访问的值存储起来,接下来使用就不会再引发回流。
// 例如myElement元素沿对角线移动,每次移动一个像素。到500*500像素的位置结束。timeout循环体中可以这么做
myElement.style.left = 1 + myElement.offsetLeft + 'px';
myElement.style.top = 1 + myElement.offsetTop + 'px';
if(myElement.offsetLeft >= 500){
stopAnimation();
}
// 显然这种方法低效,每次移动都要查询偏移量,导致浏览器刷新渲染队列而不利于优化。好的办法是获取一次起始位置的值,然后赋值给一个变量。如下
var current = myElement.offsetLeft;
current++;
myElement.style.left = current + 'px';
myElement.style.top = current + 'px';
if(myElement.offsetLeft >= 500){
stopAnimation();
}
懒执行就是将某些逻辑延迟到使用时再计算。该技术可以用于首屏优化,对于某些耗时逻辑并不需要在首屏就使用的,就可以使用懒执行。懒执行需要唤醒,一般可以通过定时器或者事件的调用来唤醒。
懒加载就是将不关键的资源延后加载。
懒加载的原理就是只加载自定义区域(通常是可视区域,但也可以是即将进入可视区域)内需要加载的东西。对于图片来说,先设置图片标签的 src 属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为 src 属性,这样图片就会去下载资源,实现了图片懒加载。
懒加载不仅可以用于图片,也可以使用在别的资源上。比如进入可视区域才开始播放视频等等。
减少像素点、减少每个像素点能够显示的颜色
静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie。
语法:
window.requestAnimationFrame(callback);
callback:
下一次重绘之前更新动画帧所调用的函数(即上面所说的回调函数)。该回调函数会被传入DOMHighResTimeStamp参数,该参数与performance.now()的返回值相同,它表示requestAnimationFrame() 开始去执行回调函数的时刻。
返回值:
一个 long 整数,请求 ID ,是回调列表中唯一的标识。是个非零值,没别的意义。你可以传这个值给 window.cancelAnimationFrame() 以取消回调函数。
<!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>
</head>
<body>
<ul>
数据条渲染:
</ul>
<script>
setTimeout(() => {
// 插入千万条数据
const total = 10000000
// 一次插入 20 条,如果觉得性能不好就减少
const once = 20
// 渲染数据总共需要几次
const loopCount = total / once
var countOfRender = 0
var ul = document.querySelector('ul')
console.time('loopTime');
function add() {
// 优化性能,插入不会造成回流
const fragment = document.createDocumentFragment()
for (var i = 0; i < once; i++) {
const li = document.createElement('li')
li.innerText = Math.floor(Math.random() * total)
fragment.appendChild(li)
}
ul.appendChild(fragment)
countOfRender += 1
loop()
}
//获取代码的执行时间
console.timeEnd('loopTime');
function loop() {
if (countOfRender < loopCount) {
window.requestAnimationFrame(add)
}
}
loop()
}, 0)
</script>
</body>
</html>
答:跨站脚本攻击就是xss攻击。有三种攻击方式:反射、注入、存储。 反射型:反射型XSS通常出现在网站的搜索栏,使用url地址挂载恶意的参数,一般主要是偷取cookie等。 注入:是在标签上使用属性添加的方式,恶意的破坏网站的数据。 存储:在接口请求中加入恶意代码,攻击或存储到数据库里。攻击,比如植入一段删除sql的语句;储存,把一段js脚本语法,早期很多网站有留言板,论坛,只要把内容中写入脚本,那么下一个来访问的人,他的信息就可能会被偷走。
标签攻击,尽量不要使用innerHTML这个语法。 url:需要在接口处做参数的判断和url校验 存储:转移,把他变成字符串,使用正则把内容种<>变成xx%字符
1.IOS 移动端 click 事件 300ms 的延迟相应 =》使用touchsyart、toychend、touchmove、tap(模拟事件)来取代cilidk事件!FastClick轻量级库
2.h5 底部输入框被键盘遮挡问题
3.CSS 动画页面闪白,动画卡顿
4.phone 及 ipad 下输入框默认内阴影
element{
-webkit-appearance:none;
}
5.防止手机中页面放大和缩小
<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="initial-scale=1,maximum-scale=1">
6.上下拉动滚动条时卡顿、慢
body{
-webkit-overflow-scrolling:touch;
overflow-scrolling:touch;
}
7.长时间按住页面出现闪退
element{
-webkit-touch-callout:none;
}
7.Android手机圆角失效
background-clip:padding-box;
前端工程化就是为了让前端开发能够“自成体系”,个人认为主要应该从模块化、组件化、规范化、自动化四个方面思考。
汇聚有些黄金灿灿的车子,华丽朴素,就是四个轱辘,一身正气吹响面试造飞机号角,启动上班拧螺丝的征程!!!
请瞄一瞄javascript的第七点
内存泄漏memoryleak:指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
内存溢出outofmemory:指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即内存溢出。
内存泄漏会导致内存溢出,内存泄漏是多个申请了内存空间的变量一直在占用,却无法被释放,也就是说内存泄漏是一个过程,进而导致内存溢出,内存溢出是结果,无法继续申请内存空间,内存占满了。
几种常见的js内存泄露:
1、意外的全局变量
JavaScript 处理未定义变量的方式比较宽松:未定义的变量会在全局对象创建一个新变量。在浏览器中,全局对象是 window ,
例如:
haorooms ="这是一个全局的haorooms"
实际上生成了一个全局的haorooms,虽然一个简单的字符串,无伤大雅,也泄露不了多少内存,但是我们在编程中尽量少的避免全局变量!
另外一种全局变量可能由this创建。例如:
function foo() {
this.variable = "potential accidental global";
}
// Foo 调用自己,this 指向了全局对象(window)
foo();
2、没有及时清理的计时器或回调函数
setInterval用多了,会占用大量的内存。因此setInterval我们必须及时清理!可以用如下方式清理setInterval。
function b() {
var a = setInterval(function() {
console.log("Hello");
clearInterval(a);
b();
}, 50);
}
b();
或者用2个函数:
function init()
{
window.ref = window.setInterval(function() { draw(); }, 50);
}
function draw()
{
console.log(\'Hello\');
clearInterval(window.ref);
init();
}
init();
或者我们用setTimeout:
function time(f, time) {
return function walk() {
clearTimeout(aeta);
var aeta =setTimeout(function () {
f();
walk();
}, time);
};
}
time(updateFormat, 1000)();
3、脱离 DOM 的引用
有时,保存 DOM 节点内部数据结构很有用。假如你想快速更新表格的几行内容,把每一行 DOM 存成字典(JSON 键值对)或者数组很有意义。此时,同样的 DOM 元素存在两个引用:一个在 DOM 树中,另一个在字典中。将来你决定删除这些行时,需要把两个引用都清除。
var elements = {
button: document.getElementById(\'button\'),
image: document.getElementById(\'image\'),
text: document.getElementById(\'text\')
};
function doStuff() {
image.src = \'http://some.url/image\';
button.click();
console.log(text.innerHTML);
// 更多逻辑
}
function removeButton() {
// 按钮是 body 的后代元素
document.body.removeChild(document.getElementById(\'button\'));
// 此时,仍旧存在一个全局的 #button 的引用
// elements 字典。button 元素仍旧在内存中,不能被 GC 回收。
}
4、闭包
function foo() {
var local = 'Hello';
return function() {
return local;
};
}
var bar = foo();
console.log(bar()); //=> Hello
/**这里所展示的让外层作用域访问内层作用域的技术便是闭包(Closure)。得益于高阶函数的应用,使foo()函数的作用域得到`延伸`。foo()函数返回了一个匿名函数,该函数存在于foo()函数的作用域内,所以可以访问到foo()函数作用域内的local变量,并保存其引用。而因这个函数直接返回了local变量,所以在外层作用域中便可直接执行bar()函数以获得local变量。**/
闭包是JAVASCRIPT的高级特性,因为把带有内部变量引用的函数带出了函数外部,所以该作用域内的变量在函数执行完毕后的并不一定会被销毁,直到内部变量的引用被全部解除。所以闭包的应用很容易造成内存无法释放的情况。
5、echart不停调用导致内存泄露
不停的用setInterval调用echart,更新echart表格及地图数据,及时清理了setInterval,也会导致内存泄露!
解决办法:
1、使用 Timeline 记录可视化内存泄漏
使用 Chrome DevTools 的 Timeline 面板可以记录和分析您的应用在运行时的所有活动。 这里是开始调查应用中可觉察性能问题的最佳位置。
2、。。。。。
请瞄一瞄前面的性能优化
SSr服务器端渲染(Server-Side Rendering)是指由服务侧完成页面的 HTML 结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程。
例如Vue SSR 组件加载问题:
test.vue:
<template>
<div>
<h2>clientHeight: {{ clientHeight }} px </h2>
</div>
</template>
<script type="text/babel">
export default {
data(){
return {
}
},
computed :{
clientHeight() {
return document.body.clientHeight;
}
},
mounted(){
}
}
</script>
上面 test.vue 组件通过 Vue computed 属性 clientHeight 直接获取 document 的文档高度,这段代码在前端渲染是不会报错的,也能拿到正确的值。但如果把这个组件放到 SSR(Server Side Render) 模式下, 就会报如下错误:
ReferenceError: document is not defined
解决方案:
通过 typeof 判断是否是存在 document 对象, 如果存在则执行后面代码。 这种方式虽然能解决问题, 但在 Webpack 构建压缩时, 不会执行的代码不会被剔除,也会打包到 js 文件中去, 因为这个是在运行期才知道结果的, 所以在 Webpack 构建方案中,不建议使用 typeof 方式判断。而是使用 Webpack 提供的 webpack.DefinePlugin 插件定义常量解决。
clientHeight() {
return typeof document === 'object' ? document.body.clientHeight : '';
}
使用 Webpack 提供的 webpack.DefinePlugin 插件定义常量解决。 这里直接使用 easywebpack https:// github.com/hubcarl/easy webpack 内置的全局 Webpack 常量 EASY_ENV_IS_BROWSER http:// hubcarl.github.io/easyw ebpack/webpack/env 进行 判断。 这样在构建压缩期间, 如果是 Node 模式构建, EASY_ENV_IS_BROWSER 会被替换为 false,如果是 Browser 模式构建, EASY_ENV_IS_BROWSER 会被替换为 true,最后构建后代码也就是变成了 true 或者 false 的常量。 因为这个是构建期间执行的,压缩插件剔除永远不会被执行的代码, 也就是
dead_code
clientHeight() {
return EASY_ENV_IS_BROWSER ? document.body.clientHeight : '';
}
NPM Vue 组件 SSR 支持
针对上面这种自己写的代码,我们可以通过这种方式解决,因为可以直接修改。但如果我们引入的一个 npm Vue 插件想进行SSR渲染, 但这个插件里面使用了 window/docment 等浏览器对象, 并没有对 SSR 模式进行兼容,这个时候该如何解决呢?
一般我们通过 通过 v-if 来决定是否渲染该组件 和 Vue 只在前端挂载组件解决问题 可以解决。
通过 v-if 来决定是否渲染该组件:
<template>
<div v-if="isBrowser">
<Loading></Loading>
</div>
</template>
<script type="text/babel">
export default {
componets:{
Loading: () =>import('vue-loading');
}
data(){
return {
isBrowser: EASY_ENV_IS_BROWSER
}
},
mounted(){
}
}
</script>
Vue 只在前端挂载组件解决问题:
<template>
<div>
<Loading></Loading>
</div>
</template>
<script type="text/babel">
export default {
data(){
return {
}
},
beforeMount() {
// 只会在浏览器执行
this.$options.components.Loading = () =>import('vue-loading');
},
mounted(){
}
}
</script>
oading 组件因为没有注册, 在 SSR 模式, 会被原样输出到 HTML 中,不会报错且不能被浏览器识别, 在显示时不会有内容。当 SSR 直出 HTML 后,浏览器模式中执行 beforeMount 挂载组件, 从而达到解决服务端渲染报错的问题
请瞄一瞄前面的性能优化第2点和第3点
Map 和 WeakMap 是两种数据结构,可用于操纵键和值之间的关系。
区别
我们可以对 Map 的键和值使用对象或任何基本类型。但是,WeakMap 仅接受对象。这意味着我们不能将基本类型用作 WeakMap 的键。
const attrs = new WeakMap()
attrs.set('color', 'plum') // error
与 Map不同,WeakMap 不支持对键和值进行迭代。无法获取 WeakMap 的所有键或值。此外,也没有办法清除 WeakMap。
最重要的区别是,WeakMap 不会阻止在没有对键的引用时对键进行垃圾收集。
另一方面,Map 无限期地维护对键和值的引用。一旦创建了键和值,它们将占用内存,即使没有对它们的引用,也不会被垃圾收集。这可能会导致内存泄漏问题。
考虑下面的一个简单代码,我们将一个唯一的 ID 映射到特定的人的信息:
let id = { value: 1 }
const people = new Map()
people.set(id, {
name: 'Foo',
age: 20,
address: 'Bar'
})
// 移除 id
id = null
删除键对象 id 后,它仍然能够通过映射键访问其引用:
people.keys().next().value // { value: 1 }
由于这种差异,WeakMap(顾名思义)保存对键的弱引用。它解释了为什么它的键不可枚举,这在前面的区别中已经提到。
由于 WeakMap 保存对键的弱引用,且无法枚举,因此无法使用 keys()、values()、entries() 这些方法。
整理中。。。
http://www.javashuo.com/article/p-hihwtfzt-t.html
Babel 是 JavaScript 编译器:他能让开发者在开发过程中,直接使用各类方言(如 TS、Flow、JSX)或新的语法特性,而不需要考虑运行环境,因为 Babel 可以做到按需转换为低版本支持的代码;Babel 内部原理是将 JS 代码转换为 AST,对 AST 应用各种插件进行处理,最终输出编译后的 JS 代码。
路由懒加载的主要原理就是原本的Vue模块是全部导入在一起的打包文件,运行后用户查看相关模块显示的内容时会将整个打包的文件引入而后在其中查找对应的模块然后才将其呈现给用户。这样会使得在打包文件中查找对应模块时,在浏览器中可能会出现短暂的空白页,从而降低用户体验。而路由懒加载是将各个模块分开打包,在用户查看下相关模块内容时就直接引入相关模块的打包文件然后进行显示,从而有效的解决了浏览器可能出现短暂时间空白页的情况。
原声js的类,静态方法继承
//es5中的类和静态方法
function Person(name,age) {
//构造函数里面的方法和属性
this.name=name;
this.age=age;
this.run=function(){
console.log(`${this.name}---${this.age}`)
}
}
//原型链上面的属性和方法可以被多个实例共享
Person.prototype.sex='男';
Person.prototype.work=function(){
console.log(`${this.name}---${this.age}---${this.sex}`);
}
//静态方法
Person.setName=function(){
console.log('静态方法');
}
var p=new Person('zhangsan','20'); /*实例方法是通过实例化来调用的,静态是通过类名直接调用*/
p.run();
p.work();
Person.setName(); /*执行静态方法*/
//es5继承
/*
原型链继承和对象冒充继承
对象冒充继承:没法继承原型链上面的属性和方法
原型链继承:可以继承构造函数里面以及原型链上面的属性和方法,实例化子类的时候没法给父类传参
* */
function Person(name,age) {
this.name=name;
this.age=age;
this.run=function(){
console.log(this.name+'---'+this.age);
}
}
Person.prototype.work=function(){
console.log('work');
}
function Web(name,age){
Person.call(this,name,age); /*对象冒充实现继承*/
}
Web.prototype=new Person();
var w=new Web('李四',20);
w.run();
w.work(); //w.work is not a function
es6中的类、静态方法 继承
//定义Person类
class Person{
constructor(name,age) { /*类的构造函数,实例化的时候执行,new的时候执行*/
this._name=name;
this._age=age;
}
getName(){
console.log(this._name);
}
setName(name){
this._name=name
}
}
var p=new Person('张三1','20');
p.getName();
p.setName('李四');
p.getName();
//es6里面的继承
class Person{
constructor(name,age){
this.name=name;
this.age=age;
}
getInfo(){
console.log(`姓名:${this.name} 年龄:${this.age}`);
}
run(){
console.log('run')
}
}
class Web extends Person{ //继承了Person extends super(name,age);
constructor(name,age,sex){
super(name,age); /*实例化子类的时候把子类的数据传给父类*/
this.sex=sex;
}
print(){
console.log(this.sex);
}
}
var w=new Web('张三','30','男');
w.getInfo();
//es6里面的静态方法
class Person{
constructor(name){
this._name=name; /*属性*/
}
run(){ /*实例方法*/
console.log(this._name);
}
static work(){ /*静态方法*/
console.log('这是es6里面的静态方法');
}
}
Person.instance='这是一个静态方法的属性';
var p=new Person('张三');
p.run();
Person.work(); /*es6里面的静态方法*/
console.log(Person.instance);
单例模式
//单例只执行一次构造函数 这样咱们以后在连接数据库的时候不会重复连接从而导致的资源浪费
class Db {
static getInstance(){ /*单例*/
if(!Db.instance){
Db.instance=new Db();
}
return Db.instance;
}
constructor(){
console.log('实例化会触发构造函数');
this.connect();
}
connect(){
console.log('连接数据库');
}
find(){
console.log('查询数据库');
}
}
var myDb=Db.getInstance();
var myDb2=Db.getInstance();
var myDb3=Db.getInstance();
var myDb4=Db.getInstance();
myDb3.find();
myDb4.find();
说instanceof 就想到typeof ,这里也介绍下typeof:,typeof是用来判断数据类型的,就一个参数 ,使用方式像这样: typeof num, 就是判断num是什么类型
typeof 一般只能返回如下几个结果**:“number”、“string”、“boolean”、“object”、“function” 和 “undefined”**; 除了"object" 其他都好说。
着重看这几个:
typeof 不存在的变量 = “undefined”
typeof 对象 = “object”
typeof null = “object”
typeof 数组 = “object”
typeod 方法的实例(比如 new Array()) =“object”
对象,数组 都是引用类型, 使用typeof 结果是 object类型,但是null 是基本数据类型,使用typeof结果也是 object,
可以这么理解:null 是 不指向任何对象 的 空指针, 因为它是指向对象的,所以typeof 就是 object, 但是它又是空的,所以就属于基本数据类型。但是要想判断一个变量是不是数组, 或者对象, 这时候就需要instanceof了(判断是不是null,直接用 变量 === null 就行, null===null 结果是 true)
现在说instanceof, 要想从根本上了解 instanceof 的奥秘,需要从两个方面着手:
1 语言规范中是如何定义这个运算符的。
2 JavaScript 原型继承机制。
JavaScript instanceof 语言规范 (简化版) 的运算代码如下:
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype;
L = L.__proto__;
while (true) {
if (L === null)
return false;
if (O === L) // 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}
规则简单来说就是 L的 proto 是不是强等于 R.prototype,不等于再找 L.proto .proto 直到 proto 为 null
模拟实现instanceof
对于用 typeof 就可以判断出来数据类型的这里就不处理,只处理 typeof 结果为 object ,并且不是 null 的。
方法一: 直接使用instanceof的规则
<script type="text/javascript">
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype;
L = L.__proto__;
while (true) {
if (L === null)
return false;
if (O === L) // 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}
// 开始测试
var a = []
var b = {}
function Foo(){}
var c = new Foo()
function child(){}
function father(){}
child.prototype = new father()
var d = new child()
console.log(instance_of(a, Array)) // true
console.log(instance_of(b, Object)) // true
console.log(instance_of(b, Array)) // false
console.log(instance_of(a, Object)) // true
console.log(instance_of(c, Foo)) // true
console.log(instance_of(d, child)) // true
console.log(instance_of(d, father)) // true
</script>
方法二:在方法一的基础上使用 constructor (此方法无法用于判断继承)
<script type="text/javascript">
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R;
L = L.__proto__;
while (true) {
if (L === null)
return false;
if (O === L.constructor) // 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}
// 开始测试
var a = []
var b = {}
function Foo(){}
var c = new Foo()
function child(){}
function father(){}
child.prototype = new father()
var d = new child()
console.log(instance_of(a, Array)) // true
console.log(instance_of(b, Object)) // true
console.log(instance_of(b, Array)) // false
console.log(instance_of(a, Object)) // true
console.log(instance_of(c, Foo)) // true
console.log(instance_of(d, child)) // false 这里就是无法用于判断继承的
console.log(instance_of(d, father)) // true
</script>
主要点:
1、明确你这个函数想要干什么
2、寻找递归结束条件
3、找出函数的等价关系式
问题:
function sum0(total, i, callback) {
if(i===0){
callback(total);
return;
}
sum0(total+i, i-1, callback)
}
sum0(0, 100, value=>{
console.log(value)
})
sum0(0, 100000, value=>{
console.log(value)
})
比如上面这个例子,递归次数在100的时候,还没有问题。一旦执行100000次的时候就会发生递归栈溢出的问题。
每次sum0都会执行sum0,导致调用栈中sum0过多溢出。
方法一:
可用setTimeout,解决调用栈过多的问题:
function sum0(total, i, callback) {
if(i===0){
callback(total);
return;
}
// sum0(total+i, i-1, callback)
setTimeout(sum0, 0, total+i, i-1, callback)
}
sum0(0, 100, value=>{
console.log(value)
})
sum0(0, 100000, value=>{
console.log(value)
})
但是问题是,要等很久才能出结果。这个原理是,当sum0运行到setTimeout处,任务被存至回调队列,然后sum0被返回,sum0执行完被弹出调用栈,任务队列的任务再进入调用栈中运行,所以每次调用栈中只能运行一次sum0。不过,任务加入任务队列再到调用栈有一定延迟,一般为几毫秒到十几毫秒不等,如果按照10毫秒计算,100000个任务就要10ms*100000=1000s了。所以会迟迟不出结果。
方法二:
function sum2(total, i, callback) {
if(i===0){
callback(total);
return;
}
let part = 1000;
if(i%part ===0 ){
setTimeout(sum2, 0, total+i, i-1, callback)
}else{
sum2(total+i, i-1, callback)
}
// sum0(total+i, i-1, callback)
}
sum2(0, 100000, value=>{
console.log(value)
})
原理是:每1000次调用sum2,才会执行一次setTimeout,sum2才会被安排到任务队列中,同时调用栈不会溢出。
快速排序的时间主要耗费在划分操作上,对长度为n的区间进行划分,共需n-1次关键字的比较,时间复杂度为O(n)。
对n个元素进行快速排序的过程构成一棵递归树,在这样的递归树中,每一层最多对n个元素进行划分,所花的时间为O(n)。当初始排序数据随机分布,使每次分成的两个子区间中的元素个数大致相等时,递归树高度为log2n,快速排序呈现最好情况,即最好情况下的时间复杂度为O(nlog2n)。快速排序算法的平均时间复杂度也是O(nlog2n)。所以快速排序是一种高效的算法。
。。。
后台角度:分页加载。。。
前端角度:worker来做子线程来实现
Worker 接口是Web Workers API的一部分,代表一个后台任务,它容易被创建并向创建者发回消息。创建一个运行者只要简单的调用Worker()构造函数,指定一个脚本,在工作线程中执行
通俗点讲就是:因为js是单线程运行的,在遇到一些需要处理大量数据的js时,可能会阻塞页面的加载,造成页面的假死。这时我们可以使用worker来开辟一个独立于主线程的子线程来进行哪些大量运算。这样就不会造成页面卡死。也说明 worker可以用来解决大量运算是造成页面卡死的问题。
语法规则:
const worker=new Worker(aURL, options)
//aURL(必须)是一个DOMString 表示worker 将执行的脚本的URL。它必须遵守同源策略。
//options (可选)它的一个作用就是指定 Worker 的名称,用来区分多个 Worker 线程
属性:
方法:
例如:
求斐波纳茨数列的第38项
没有使用worker情况:
<div style="width:100px;height:100px;background-color:red;"></div>
document.querySelector('div').onclick=function(){
console.log('hello world');
}
function fibonacci(n){
return n<2?n:arguments.callee(n-1)+arguments.callee(n-2);
}
console.log(fibonacci(38));
使用了worker的情况
<div style="width:100px;height:100px;background-color:red;"></div>
var worker=new Worker('worker.js');
worker.postMessage(40);
worker.onmessage=function(event){
var data=event.data;
console.log(data)
};
worker.onerror=function(event){
console.log(event.fileName,event.lineo,event.message);
};
<!--worker.js-->
self.onmessage = function (event) {
var data = event.data;
var ans = fibonacci(data);
this.postMessage(ans);
};
function fibonacci(n) {
return n < 2 ? n : arguments.callee(n - 1) + arguments.callee(n - 2);
}