js
javascript是一种基于原型的面向对象语言,其它语言则是基于类的面向对象。
1、意外的全局变量
function(){
a=444; //a成为一个全局变量,不会被回收
}
2、闭包
因为内层哈数引用外层函数的局部变量,导致变量不会被回收,直到关闭页面。
3、没有清理的dom元素引用
4、定时器没有及时清除
5、缓存(因为缓存会不会被回收的,要做大小限制、及时清除)
6、死循环
二、闭包
1、什么是闭包
即函数外部可以访问函数内部的局部变量。
一个函数中return另一个函数,内层函数引用外层函数的变量,那么这个变量就不会被马上回收,而是被保存在内存中。
2、作用:
1、函数外部能够读取函数内部的变量,保护局部变量不会受到全局污染,利用代码的封装;
2、变量会被保存在内存中。
缺点:消耗内存,造成网页性能问题。
方法:退出函数之前,把不使用的局部变量置为null。
3、使用场景:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <button>Button0</button> <button>Button1</button> <button>Button2</button> <button>Button3</button> <button>Button4</button> </body> </html> <script> var btns = document.getElementsByTagName('button'); for(var i = 0, len = btns.length; i < len; i++) { btns[i].onclick = function() { alert(i); //每次都会弹出5,因为alert是被异步触发的,每次触发时,for循环早已结束 } } </script>
解决方法:
1、采用立即执行函数创建作用域
for(var i=0;i<btns.length;i++){ (function(){ btns[i].onclick = function() { alert(i); //依次弹出0、1、2、3、4, } })(i) }
2.把var改为let
三、js的数据类型
1、基本类型:String 、Boolean 、Number 、Undefined 、Null 、Symbol(表示独一无二的值)
基本数据类型:按值存放,栈内存中,直接访问
堆(heap):堆是在程序运行时,申请某个大小的内存空间,动态分配。数据结构.像倒过来的树。 栈(stack): 先进后出的数据结构。像桶。系统分配。 队列(queue):先进先出。特殊线性表。
Undefined==Null
Undefined==false
Null=false
Undefined:变量被声明了,但没有被赋值
Null :Null 类型的值只有一个,即null,表示一个空对象引用
2、引用类型:Object (对象、数组、函数)
保存的是一个地址指针,堆内存,根据地址指针去获取数据。obj1和obj2如果指向同一个内存地址,那么修改其中一个,另一个也会受到影响。
数据类型转换:
var arr = [undefined , true, 'world', 123 , null, new Object , function () {}]
for( i = 0; i < arr.length; i ++) {
console.log(typeof (arr[i]));
}
输出的结果为:undefined , boolean , string , number , object , object , function
typeof() 弊端:null、object、[]返回的都是object
基本数据类型--名值都存在栈内存中。 引用数据类型--名在栈中,值存在堆内存中。栈内存会提供一个引用地址指向堆内存中的值。
四、深拷贝、浅拷贝
1、定义
深拷贝:拷贝后的数据不会影响原来的数据。(对基本数据类型的拷贝)
浅拷贝:拷贝的只是一个引用地址,修改拷贝后的数据会影响原来的数据。(对引用数据类型(Object、Array)的拷贝)。
浅拷贝是只复制一层对象的属性(Object.assign({},obj)),,而深拷贝则递归复制所有的层级。
2、如何实现深拷贝
1、JSON.parse(JSON.stringify())
let obj={name:'zs',sex:'男'};
let obj2=JSON.parse(JSON.Stringify(obj));
//先把obj转换为json字符串, 相当于基本数据类型的拷贝。
//缺点:obj中不能包含函数,因为JSON.parse、JSON.Stringify不能处理函数。
2、deepCopy (递归复制所有的层级)
a instanceof Object //true 判断a是不是Object的实例
function deepClone(obj){ if(obj instanceof Object){ //如果是个对象 let tep={}; for(var key in obj){ if(obj.hasOwnProperty(key)){ tep[key]=deepClone(obj[key]) } } return tep; }else if(obj instanceof Array){ //如果是个数组 let arr=[]; for(let i=0;i<obj.length;i++){ arr.push(deepClone(obj[i])); } return arr; }else if(obj instanceof Function){ //如果是个函数 return new Function('return '+obj.toString()); }else{ //如果是基本数据类型 return obj; } }
五、原型链
1、执行流程:
js去查找一个对象的属性时:
(1)先查找实例里的属性和方法,如果有就返回;
(2)若没有,就通过proto去到构造函数的原型对象(prototype)去找,没有的话就会返回undefined。
2、_ proto _属性和prototype属性的区别
prototype是构造函数的属性;
_ proto_ 是实例对象里的隐式属性,在new这个实例的时候, _proto _指向prototype所指的对象。
3、构造函数、原型对象和实例对象的关系
构造函数.prototype==原型对象;
原型对象.constructor==构造函数
构造函数.isPrototypeOf(实例对象) //判断某个实例是不是属于这个构造函数
function Person(name){} Person.prototype={ constructor:Person, name:'ss', sayName:function(){ console.log(33); } }; var p1=new Person(); var p2=new Person(); console.log(p1.__proto__==Person.prototype); //true //prototype是p1和p2的共享的原型对象,p1和p2都有一个__proto__属性,指向Person的prototype console.log(Person.prototype);//Person console.log(Person.prototype.constructor);// ƒ Person(name){} //原型对象内部有一个指针(constructor属性)指向构造函数
六、继承
1、原型链继承:
将父类的实例作为子类的原型
function Father(){ this.names = ["aa", "bb"]; //引用类型值 this.say=function(){ return this.name; } } Father.prototype={ constructor:Father, hei:200, eat:function(){ rturn '333' } } function Son(age){ this.age=age; } Son.prototype=new Father(); var son1 = new Son(); son1.names.push("cc"); console.log(son1.names); //["aa","bb","cc"] var son2 = new Son(); console.log(son2.names); //["aa","bb","cc"]
缺点:
1、不能给父类传参;
2、引用类型的属性被所有实例共享
2、借用构造函数继承
function Son(age){
Father.call(this,age);
this.age=age;
}
特点:
1、可以传参,也避免了引用类型的属性被所有实例共享;
2、但没有继承父类原型上的属性和方法
3、组合继承
原型+借用构造函数
特点:
1、调用两次父类构造函数
2、占用内存
function Father(name){ this.name=name } function Son(name,age){ Father.call(this,name); //借用构造函数,执行父类构造函数 this.age=age; } Son.prototype=new Father(); //原型继承,既继承父类的构造函数,又继承父类的原型
4、寄生组合继承
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto。 语法:Object.create(proto, [propertiesObject])
function Son(age){ Father.call(this,age);//借用构造函数继承了父类构造函数里的属性和方法 this.age=age; } //寄生继承了原型里的属性和方法 Son.prototype=Object.create(Father.prototype,{ constructor:{ value:Son } }) //或者: Son.prototype=Object.create(Father.prototype); Son.prototype.constructor=Son Son.prototype.sayHi=function(){} //原型多继承:Object.assign() function Son() { Father.call(this); OhterClass1.call(this); OhterClass2.call(this); } Son.prototype = Object.create(Father.prototype); Object.assign(Son.prototype, OhterClass1.prototype, OhterClass2.prototype); Son.prototype.constructor = Son;
九、call和apply和bind
这三个方法都是Function.prototype下的方法,用于改变函数运行时的上下文,即改变this的指向。
window.name = '李一灵'; document.name = '李流云'; const obj = {name:'王二蛋'} //声明一个函数 let fn = function (){ console.log(this.name) } fn()// 李一灵 fn.call(this)//当前this 指向的浏览器的全局对象window 李一灵 fn.call(document) // '李流云' fn.call(obj) //王二蛋 //第一个参数为null或者undefined,则默认指向window 区别: apply第二个参数为数组,call为参数列表 那么apply第二个参数可以写arguments,即实参列表。 function aa(num){ fn.apply(this,arguments) //arguments==[5] fn.call(this,num) } aa(5); bind与call和apply的区别: bind也是改变函数上下文,但不是立即执行,而是等需要时再调用。
十、for in与for循环
for in:
1、for in,index索引为字符串而非数字
2、遍历的顺序可能不按数组的顺序,
3、把实例和原型里可枚举属性都遍历,性能开销增大(constructor是不可枚举的,enumerable:fasle)
forEach、for in、for of 三者区别
forEach更多用来遍历数组
for in用来遍历对象或json
for of 数组对象都可以遍历,遍历的是value
for in 遍历的是key
十一、new
1、创建了一个新的空对象;
2、新对象的proto指向构造函数的prototype
3、构造函数内this指向新创建的对象;
4、返回新对象的地址。
function Person(name) { console.log(this) // this==p; this.name=='ss' this.name = name; } var p=new Person('ss'); p._proto_==Person.prototype;
十二、this
this是什么:
this是函数被调用时自动生成的一个内部对象,只能在函数内部使用。
普通函数:一般指向调用时所处的环境对象(谁调用我,我就是谁);
箭头函数:指向定义时所处的环境对象。
1、全局作用域或者普通函数中,this指向window
//直接打印 console.log(this) //window //function声明函数 function bar () {console.log(this)} bar() //window //function声明函数赋给变量 var bar = function () {console.log(this)} bar() //window //自执行函数 (function () {console.log(this)})(); //window
2、方法调用中,谁调用this指向谁
//对象方法调用 var person = { run: function () {console.log(this)} } person.run() // person //事件绑定 var btn = document.querySelector("button") btn.onclick = function () { console.log(this) // btn } //事件监听 var btn = document.querySelector("button") btn.addEventListener('click', function () { console.log(this) //btn }) //jquery的ajax $.ajax({ self: this, type: "get", url: url, async: true, success: function (res) { console.log(this) // this指向传入$.ajxa()中的对象obj console.log(self) // window } }); //这里说明以下,将代码简写为$.ajax(obj) ,在obj中this指向window,因为在在success方法中,独享obj调用自己,所以this指向obj
3、构造函数中,this指向构造函数的实例
//不使用new指向window function Person(name) { console.log(this) // window this.name = name; } Person('inwe') //使用new function Person(name) { this.name = name console.log(this) //people self = this } var people = new Person('iwen') console.log(self === people) //true //这里new改变了this指向,将this由window指向Person的实例对象people
4、箭头函数中,指向外层作用域的this(箭头函数没有自己的this和arguments,所以它引用的是外层的this和arguments)
var obj = { foo() { console.log(this); }, bar: () => { console.log(this); } } obj.foo() // {foo: ƒ, bar: ƒ} obj.bar() // window
十三、作用域和作用域链
作用域:有权访问的一个范围。
作用域链:每个执行环境可以通过向上查找,搜索变量和函数,而不能向下搜索,就近原型,最顶层是window。
十四、防抖、节流
防抖:将多次操作合并为一个操作。原理:维护一个定时器,规定在delay时间后执行,如果在这个时间段内触发,就会重新设置定时器,只有最后一次操作会被执行。缺点:如果在规定时间内不断出发,则调用方法会被不断延迟。
function debounce(fn,delay){ var timer=null; clearTimerOut(timer); //每次进来都先清除定时器,再开始计时 return function(){ timer=setTimerOut(()=>{ fn.call(this,arguments); },delay); } } function handleScroll(){ console.log('函数防抖'); } window.addEventListener('scroll',debounce(handleScroll,2000));
节流:规定在多久后再执行,如果当前有正在执行的回调函数,则return。防止重复点击导致发送多个请求。
function throttle(){ let ajaxFlag=false; return function(){ if(ajaxFlag) return; ajaxFlag=true; setTimeOut(()=>{ ajaxFlag=false; //成功之后设置为fasle,表示可以再次执行了 },1000); } }
十六、懒加载
原理:
只加载窗口可视区域的图片,<img>设置一个自定义属性存放图片路径,当浏览器可视区域移动到此图片时,再将data-src的路径赋值给src,此时图片才会真正的加载。
作用:
加快当前可视区域图片的加载速度,优化用户体验。
减少同一时间发送服务器的请求数量,减小服务器的压力。
十七、立即执行函数
定义:声明一个匿名函数,然后执行它
作用:创建一个独立的作用域,外部访问不到,保护了变量不被全局污染。
十八、事件代理
定义:
利用事件冒泡的原理,把事件处理函数绑定在父级上,目的是提高性能。
(html元素是嵌套结构,在触发内层元素的事件时,外部事件也会被由内到外触发,这种现象叫做事件冒泡)
作用:
1、减少事件注册,减少dom操作,节省内存.(
js中添加到页面的事件处理程序数量直接关系到页面的整体性能, 因为需要不断地与dom进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长页面的交互就绪时间。
js中每个函数都是一个对象,对象越多,内存占用就越大,性能开销越大。
)
2、动态生成的子元素不用为其添加事件。
var oUL=document.getElementById("test"); oUL.addEventListener("click",function(ev){ var target=ev.target; while(target!=='oUL'){ if(target.tagName.toLowerCase=='li'){ doSomething()// break; } target=target.parentNode; },false //true,事件会在捕获阶段执行 //false,事件会在冒泡阶段执行(默认) })
阻止冒泡
e.stopPropagation()
阻止默认事件
e.preventDefault()
DOM2.0模型事件处理流程:
捕获阶段=>目标阶段=>冒泡阶段
document=>body=>dom(捕获阶段,从上往下)
target(事件源)
target=>parent=>body=>document(冒泡阶段,从下往上)
浏览器渲染流程
html文档包括html标签、css、以及javascript,浏览器引擎会从上到下逐行解析,遇到html标签和css样式,就交给GUI渲染线程去执行,遇到javascript代码就交给js引擎线程去执行,其中GUI渲染线程和js引擎线程是互斥的,不能同时进行。
1、构建DOM树:根据html标签建立(文档对象模型);
2、构建css规则树:包括选择器和样式的属性;
3、根据dom树和css规则树,构建render tree;
4、布局:根据渲染树计算元素的位置和大小
5、渲染:根据render tree和布局进行渲染。
整个前端性能提升大致分几类
1、静态资源的优化
主要是减少静态资源的加载时间,主要包括html、js、css和图片。
a、减少http请求数:合并js、css、制作雪碧图以及使用http缓存;
b、减小资源的大小:压缩文件、压缩图片,小图使用base64编码等;
c、异步组件和图片懒加载;
d、CDN加速和缓存(bootCND):客户端可通过最佳的网络链路加载静态资源,提高访问的速度和成功率。
(CDN:通过在网络各处放置节点服务器构成的一层智能虚拟网络,可将用户的请求重新导向离用户最近的服务节点上)
2、接口访问的优化
1、http持久链接(Conection:keep-alive)
2、后端优化合并请求(如果页面中有两个请求返回数据一样的,那么可以考虑合并请求)
3、冷数据接口缓存到localstorage,减少请求
3、页面渲染速度的优化
1、由于浏览器的js引擎线程和GUI渲染线程是互斥的,所以在执行js的时候会阻塞它的渲染,所以一般
会将css放在顶部,优先渲染,js放在底部;
2、减少dom的操作:
a、vue中使用了虚拟DOM渲染方案,做到最小化操作真实的dom;
b、事件代理:利用事件冒泡原理,把函数注册到父级元素上。
3、减少页面的重绘和回流。
vue里:
1、souceMap关闭,只打包压缩后的文件;
2、compression-webpack-plugin,并设置productGzip:true,开启压缩;
3、路由懒加载(加快首屏加载速度,缺点:把多个js分开打包,导致http请求数增多)
4、v-if和v-show:
v-if是懒加载,只有为true时才加载,false时不会占据布局空间
v-show:不管是true还是false都会渲染,并会占据布局空间,优点:减少页面的重绘和回流。
5、为item设置key值:在列表数据进行遍历渲染的时候,方便vue将新值和旧值做对比,只渲染变化了的部分。
6、组件细分(比如轮播组件、列表组件、分页组件等):当数据变更时,渲染会加快;其次易于组件复用和维护。
7、使用loading和骨架屏加载,防止白屏和闪屏的情况。
8、服务端渲染:vue的页面渲染,通过模板编译,渲染出页面,而不是直出html,对于首屏有较大的损耗。
服务端渲染:现在服务端进行整个html的渲染,再将整个html输出到浏览器端。
9、DNS缓存,减少dns查询时间。