zoukankan      html  css  js  c++  java
  • 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查询时间。

     

  • 相关阅读:
    钱多,人傻,快来快来
    Rabbitmq的使用及Web监控工具使用
    Fiddler的配置
    哪个微信编辑器比较好用?
    js手机号批量滚动抽奖代码实现
    Webform和MVC,为什么MVC更好一些?
    自学MVC看这里——全网最全ASP.NET MVC 教程汇总
    客如云系统访谈
    Asp.Net MVC2.0 Url 路由入门---实例篇
    架设自己的FTP服务器 Serv-U详细配置图文教程
  • 原文地址:https://www.cnblogs.com/annie211/p/14701138.html
Copyright © 2011-2022 走看看