前端基本功-罕见观点(三)

前端基础功-罕见观点(一) 点这里
前端基础功-罕见观点(二) 点这里
前端基础功-罕见观点(三) 点这里

1.HTML / XML / XHTML

  • html:超文本标记言语,显现信息,不辨别大小写
  • xhtml:升级版的html,辨别大小写
  • xml:可扩大标记言语被用来传输和存储数据

2.AMD/CMD/CommonJs/ES6 Module

  • AMD:AMD范例采纳异步体式格局加载模块,模块的加载不影响它背面语句的运转。一切依靠这个模块的语句,都定义在一个回调函数中,比及加载完成以后,这个回调函数才会运转。

    AMD是requirejs 在推行过程当中对模块定义的范例化产出,提早实行,推重依靠前置。用define()定义模块,用require()加载模块,require.config()指定援用途径等

    起首我们须要引入require.js文件和一个进口文件main.js。main.js中设置require.config()并划定项目中用到的基础模块。

        /** 网页中引入require.js及main.js **/
        
        <script src="js/require.js" data-main="js/main"></script>
        
        /** main.js 进口文件/主模块 **/
        // 起首用config()指定各模块途径和援用名
        require.config({
          baseUrl: "js/lib",
          paths: {
            "jquery": "jquery.min",  //现实途径为js/lib/jquery.min.js
            "underscore": "underscore.min",
          }
        });
        // 实行基础操纵
        require(["jquery","underscore"],function($,_){
          // some code here
        });

    援用模块的时刻,我们将模块名放在[]中作为reqiure()的第一参数;如果我们定义的模块自身也依靠其他模块,那就须要将它们放在[]中作为define()的第一参数。

        // 定义math.js模块
        define(function () {
            var basicNum = 0;
            var add = function (x, y) {
                return x + y;
            };
            return {
                add: add,
                basicNum :basicNum
            };
        });
        // 定义一个依靠underscore.js的模块
        define(['underscore'],function(_){
          var classify = function(list){
            _.countBy(list,function(num){
              return num > 30 ? 'old' : 'young';
            })
          };
          return {
            classify :classify
          };
        })
            
        // 援用模块,将模块放在[]内
        require(['jquery', 'math'],function($, math){
          var sum = math.add(10,20);
          $("#sum").html(sum);
        });
  • CMD:seajs 在推行过程当中对模块定义的范例化产出,耽误实行,推重依靠就近

    require.js在申明依靠的模块时会在第一之间加载并实行模块内的代码:

        define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) { 
            // 等于在最前面声明并初始化了要用到的一切模块
            if (false) {
              // 纵然没用到某个模块 b,但 b 照样提早实行了
              b.foo()
            } 
        });

    CMD是另一种js模块化计划,它与AMD很类似,差异点在于:AMD 推重依靠前置、提早实行,CMD推重依靠就近、耽误实行。此范例现实上是在sea.js推行过程当中发作的。

        /** CMD写法 **/
        define(function(require, exports, module) {
            var a = require('./a'); //在须要时申明
            a.doSomething();
            if (false) {
                var b = require('./b');
                b.doSomething();
            }
        });
        
    
        /** sea.js **/
        // 定义模块 math.js
        define(function(require, exports, module) {
            var $ = require('jquery.js');
            var add = function(a,b){
                return a+b;
            }
            exports.add = add;
        });
        // 加载模块
        seajs.use(['math.js'], function(math){
            var sum = math.add(1+2);
        });
  • CommonJs:Node.js是commonJS范例的主要实践者,它有四个主要的环境变量为模块化的完成供应支撑:module、exports、require、global。现实运用时,用module.exports定义当前模块对外输出的接口(不引荐直接用exports),用require加载模块。

        // 定义模块math.js
        var basicNum = 0;
        function add(a, b) {
          return a + b;
        }
        module.exports = { //在这里写上须要向外暴露的函数、变量
          add: add,
          basicNum: basicNum
        }
        
        // 援用自定义的模块时,参数包含途径,可省略.js
        var math = require('./math');
        math.add(2, 5);
        
        // 援用中心模块时,不须要带途径
        var http = require('http');
        http.createService(...).listen(3000);

    commonJS用同步的体式格局加载模块。在服务端,模块文件都存在当地磁盘,读取异常快,所以如许做不会有题目。然则在阅读器端,限于网络缘由,更合理的计划是运用异步加载。

  • ES6 Module:ES6 在言语规范的层面上,完成了模块功用,而且完成得相称简朴,旨在成为阅读器和服务器通用的模块处置惩罚计划。其模块功用主要由两个敕令组成:export和import。export敕令用于划定模块的对外接口,import敕令用于输入其他模块供应的功用。

    /** 定义模块 math.js **/
    var basicNum = 0;
    var add = function (a, b) {
        return a + b;
    };
    export { basicNum, add };
    
    /** 援用模块 **/
    import { basicNum, add } from './math';
    function test(ele) {
        ele.textContent = add(99 + basicNum);
    }

    如上例所示,运用import敕令的时刻,用户须要晓得所要加载的变量名或函数名。实在ES6还供应了export default敕令,为模块指定默许输出,对应的import语句不须要运用大括号。这也更趋近于ADM的援用写法。

    /** export default **/
    //定义输出
    export default { basicNum, add };
    //引入
    import math from './math';
    function test(ele) {
        ele.textContent = math.add(99 + math.basicNum);
    }

    ES6的模块不是对象,import敕令会被 JavaScript 引擎静态剖析,在编译时就引入模块代码,而不是在代码运转时加载,所以没法完成条件加载。也正因为这个,使得静态剖析成为可以。

ES6 模块与 CommonJS 模块的差异

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的援用。

    • CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
    • ES6 模块的运转机制与 CommonJS 不一样。JS 引擎对剧本静态剖析的时刻,碰到模块加载敕令import,就会天生一个只读援用。比及剧本真正实行时,再依据这个只读援用,到被加载的谁人模块内里去取值。换句话说,ES6 的import有点像 Unix 体系的“标记衔接”,原始值变了,import加载的值也会随着变。因而,ES6 模块是动态援用,而且不会缓存值,模块内里的变量绑定其地点的模块。
  • CommonJS 模块是运转时加载,ES6 模块是编译时输出接口。

    • 运转时加载: CommonJS 模块就是对象;即在输入时是先加载悉数模块,天生一个对象,然后再从这个对象上面读取要领,这类加载称为“运转时加载”。

- 编译时加载: ES6 模块不是对象,而是经由过程 export 敕令显式指定输出的代码,import时采纳静态敕令的情势。即在import时可以指定加载某个输出值,而不是加载悉数模块,这类加载称为“编译时加载”。


CommonJS 加载的是一个对象(即module.exports属性),该对象只要在剧本运转完才会天生。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态剖析阶段就会天生。


本节参考文章:前端模块化:CommonJS,AMD,CMD,ES6

3.ES5的继续/ES6的继续

ES5的继续时经由过程prototype或组织函数机制来完成。ES5的继续本质上是先建立子类的实例对象,然后再将父类的要领增添到this上(Parent.apply(this))。

ES6的继续机制完全差异,本质上是先建立父类的实例对象this(所以必需先挪用父类的super()要领),然后再用子类的组织函数修正this

详细的:ES6经由过程class症结字定义类,内里有组织要领,类之间经由过程extends症结字完成继续。子类必需在constructor要领中挪用super要领,不然新建实例报错。因为子类没有本身的this对象,而是继续了父类的this对象,然后对其举行加工。如果不挪用super要领,子类得不到this对象。

ps:super症结字指代父类的实例,即父类的this对象。在子类组织函数中,挪用super后,才可运用this症结字,不然报错。

区分:(以SubClass,SuperClass,instance为例)

    • ES5中继续的本质是:(那种典范寄生组合式继续法)经由过程prototype或组织函数机制来完成,先建立子类的实例对象,然后再将父类的要领增添到this上(Parent.apply(this))。

      • 先由子类(SubClass)组织出实例对象this
      • 然后在子类的组织函数中,将父类(SuperClass)的属性增添到this上,SuperClass.apply(this, arguments)
      • 子类原型(SubClass.prototype)指向父类原型(SuperClass.prototype)
      • 所以instance是子类(SubClass)组织出的(所以没有父类的[[Class]]症结标志)
      • 所以,instance有SubClass和SuperClass的一切实例属性,以及可以经由过程原型链回溯,猎取SubClass和SuperClass原型上的要领
    • ES6中继续的本质是:先建立父类的实例对象this(所以必需先挪用父类的super()要领),然后再用子类的组织函数修正this

      • 先由父类(SuperClass)组织出实例对象this,这也是为何必需先挪用父类的super()要领(子类没有本身的this对象,需先由父类组织)
      • 然后在子类的组织函数中,修正this(举行加工),比方让它指向子类原型(SubClass.prototype),这一步很症结,不然没法找到子类原型(注,子类组织中加工这一步的现实做法是推想出的,从终究结果来推想)
      • 然后一样,子类原型(SubClass.prototype)指向父类原型(SuperClass.prototype)
      • 所以instance是父类(SuperClass)组织出的(所以有着父类的[[Class]]症结标志)
      • 所以,instance有SubClass和SuperClass的一切实例属性,以及可以经由过程原型链回溯,猎取SubClass和SuperClass原型上的要领

    静态要领继续本质上只须要变动下SubClass.__proto__到SuperClass即可

    《前端基本功-罕见观点(三)》

    本节参考文章:链接

    4.HTTP request报文/HTTP response报文

    要求报文响应报文
    要求行 要求头 空行 要求体状况行 响应头 空行 响应体
    • HTTP request报文构造是怎样的

      首行是Request-Line包含:要求要领,要求URI,协定版本,CRLF
      首行以后是多少行要求头,包含general-header,request-header或许entity-header,每一个一行以CRLF终了
      要求头和音讯实体之间有一个CRLF分开
      依据现实要求须要可以包含一个音讯实体 一个要求报文例子以下:

      GET /Protocols/rfc2616/rfc2616-sec5.html HTTP/1.1
      Host: www.w3.org
      Connection: keep-alive
      Cache-Control: max-age=0
      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
      User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36
      Referer: https://www.google.com.hk/
      Accept-Encoding: gzip,deflate,sdch
      Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
      Cookie: authorstyle=yes
      If-None-Match: "2cc8-3e3073913b100"
      If-Modified-Since: Wed, 01 Sep 2004 13:24:52 GMT
      
      name=qiu&age=25

    要求报文

    《前端基本功-罕见观点(三)》

    • HTTP response报文构造是怎样的

      首行是状况行包含:HTTP版本,状况码,状况形貌,背面跟一个CRLF
      首行以后是多少行响应头,包含:通用头部,响应头部,实体头部
      响应头部和响应实体之间用一个CRLF空行分开
      末了是一个可以的音讯实体 响应报文例子以下:

      HTTP/1.1 200 OK
      Date: Tue, 08 Jul 2014 05:28:43 GMT
      Server: Apache/2
      Last-Modified: Wed, 01 Sep 2004 13:24:52 GMT
      ETag: "40d7-3e3073913b100"
      Accept-Ranges: bytes
      Content-Length: 16599
      Cache-Control: max-age=21600
      Expires: Tue, 08 Jul 2014 11:28:43 GMT
      P3P: policyref="http://www.w3.org/2001/05/P3P/p3p.xml"
      Content-Type: text/html; charset=iso-8859-1
      
      {"name": "qiu", "age": 25}

    响应报文

    《前端基本功-罕见观点(三)》

    5.面向对象的工场形式/组织函数

    工场形式集合实例化了对象,防备实例化对象大批反复题目

    //工场形式
    function createObject(a,b){
        var obj = new Object();    //集合实例化
        obj.a = a;
        obj.b = b;
        obj.c = function () {
            return this.a + this.b;
        };
        return obj;        //返回实例化对象
    }
    var box = createObject('abc',10);
    var box1 = createObject('abcdef',20);
    alert(box.c());        //返回abc10
    alert(box1.c());       //返回abcdef20
    //组织函数
    function Create(a,b) {
        this.a =a;
        this.b =b;
        this.c = function () {
            return this.a + this.b;
        };
    }
    var box = new Create('abc',10);
    alert(box.run());    //返回abc10

    组织函数比拟工场形式:

    1. 没有集合实例化
    2. 没有返回对象实例
    3. 直接将属性和要领赋值给this
    4. 处置惩罚了对象实例归属题目

    组织函数编写范例:

    1. 组织函数也是函数,然则函数名的第一个字母大写
    2. 必需运用new运算符 + 函数名(首字母大写)比方:var box = new Create();

    组织函数和一般函数的区分:

    1. 一般函数,首字母无需大写
    2. 组织函数,用一般函数挪用体式格局无效

    检察归属题目,要建立两个组织函数:

    function Create(a,b) {
        this.a =a;
        this.b =b;
        this.c = function () {
            return this.a + this.b;
        };
    }
    
    function DeskTop(a,b) {
        this.a =a;
        this.b =b;
        this.c = function () {
            return this.a + this.b;
        };
    }
    
    var box = new Create('abc',10);
    var box1 = new DeskTop('def',20);
    alert(box instanceof Object);
    //这里要注重:一切的组织函数的对象都是Object.
    alert(box instanceof Create);    //true
    alert(box1 instanceof Create);   //false
    alert(box1 instanceof DeskTop);    //true

    6. new Promise / Promise.resolve()

    Promise.resolve()可以天生一个胜利的Promise

    Promise.resolve()语法糖

    例1:
    Promise.resolve('胜利')等同于new Promise(function(resolve){resolve('胜利')})

    例2:

    var resolved = Promise.resolve('foo');
    
    resolved.then((str) => 
        console.log(str);//foo
    )

    相称于

    var resolved = new Promise((resolve, reject) => {
       resolve('foo')
    });
    
    resolved.then((str) => 
        console.log(str);//foo
    )

    Promise.resolve要领有下面三种情势:

    • Promise.resolve(value);
    • Promise.resolve(promise);
    • Promise.resolve(theanable);

    这三种情势都邑发作一个新的Promise。个中:

    • 第一种情势供应了自定义Promise的值的才,它与Promise.reject(reason)对应。二者的差异,在于获得的Promise的状况差异。
    • 第二种情势,供应了建立一个Promise的副本的才。
    • 第三种情势,是将一个类似Promise的对象转换成一个真正的Promise对象。它的一个主要作用是将一个其他完成的Promise对象封装成一个当前完成的Promise对象。比方你正在用bluebird,然则现在有一个Q的Promise,那末你可以经由过程此要领把Q的Promise变成一个bluebird的Promise。

    现实上第二种情势可以归在第三种情势中。

    本节参考文章:ES6中的Promise.resolve()

    引荐阅读:性感的Promise…

    7.伪类 / 伪元素

    伪类

    伪类 用于当
    已有元素处于的某个状况时,为其增添对应的款式,这个状况是依据用户行动而动态变化的。

    当用户悬停在指定的元素时,我们可以经由过程 :hover 来形貌这个元素的状况。虽然它和一般的 CSS 类类似,可以为已有的元素增添款式,然则它只要处于 DOM 树没法形貌的状况下才为元素增添款式,所以将其称为伪类。

    《前端基本功-罕见观点(三)》

    伪元素

    伪元素 用于建立一些
    不在文档树中的元素,并为其增添款式。

    我们可以经由过程 :before 来在一个元素前增添一些文本,并为这些文本增添款式。虽然用户可以看到这些文本,然则这些文本现实上不在文档树中。

    《前端基本功-罕见观点(三)》

    本节参考文章:前端口试题-伪类和伪元素总结伪类与伪元素

    8.DOMContentLoaded / load

    《前端基本功-罕见观点(三)》

    DOM文档加载的步骤为:

    1. 剖析HTML构造。
    2. DOM树构建完成。//DOMContentLoaded
    3. 加载外部剧本和款式表文件。
    4. 剖析并实行剧本代码。
    5. 加载图片等外部文件。
    6. 页面加载终了。//load

    触发的机遇不一样,先触发DOMContentLoaded事宜,后触发load事宜。

    原生js

    // 不兼容老的阅读器,兼容写法见[jQuery中ready与load事宜](http://www.imooc.com/code/3253),或用jQuery
    document.addEventListener("DOMContentLoaded", function() {
       // ...代码...
    }, false);
    
    window.addEventListener("load", function() {
        // ...代码...
    }, false);

    jQuery

    // DOMContentLoaded
    $(document).ready(function() {
        // ...代码...
    });
    
    //load
    $(document).load(function() {
        // ...代码...
    });
    • head 中资本的加载

      • head 中 js 资本加载都邑住手背面 DOM 的构建,然则不影响背面资本的下载。
      • css资本不会障碍背面 DOM 的构建,然则会障碍页面的初次衬着。
    • body 中资本的加载

      • body 中 js 资本加载都邑住手背面 DOM 的构建,然则不影响背面资本的下载。
      • css 资本不会障碍背面 DOM 的构建,然则会障碍页面的初次衬着。
    • DomContentLoaded 事宜的触发
      上面只是讲了 html 文档的加载与衬着,并没有讲 DOMContentLoaded 事宜的触发机遇。直接了当地结论是,DOMContentLoaded 事宜在 html文档加载终了,而且 html 所援用的内联 js、以及外链 js 的同步代码都实行终了后触发
      人人可以本身写一下测试代码,离别援用内联 js 和外链 js 举行测试。
    • load 事宜的触发
      当页面 DOM 构造中的 js、css、图片,以及 js 异步加载的 js、css 、图片都加载完成以后,才会触发 load 事宜。

      注重:
      页面中援用的js 代码如果有异步加载的 js、css、图片,是会影响 load 事宜触发的。video、audio、flash 不会影响 load 事宜触发。

    引荐阅读:再谈 load 与 DOMContentLoaded
    本节参考文章:DOMContentLoaded与load的区分事宜DOMContentLoaded和load的区分

    9. 为何将css放在头部,将js文件放在尾部

    因为阅读器天生Dom树的时刻是一行一行读HTML代码的,script标签放在最背面就不会影响前面的页面的衬着。那末题目来了,既然Dom树完全天生好后页面才衬着出来,阅读器又必需读完全部HTML才天生完全的Dom树,script标签不放在body底部是否是也一样,因为dom树的天生须要悉数文档剖析终了。

    《前端基本功-罕见观点(三)》

    我们再来看一下chrome在页面衬着过程当中的,绿色标志线是First Paint的时候。纳尼,为何会涌现firstpaint,页面的paint不是在衬着树天生以后吗?其完成代阅读器为了更好的用户体验,衬着引擎将尝试尽快在屏幕上显现的内容。它不会比及一切HTML剖析之前最先构建和规划衬着树。部份的内容将被剖析并显现。也就是说阅读器可以衬着不完全的dom树和cssom,尽快的削减白屏的时候。如果我们将js放在header,js将壅塞剖析dom,dom的内容会影响到First Paint,致使First Paint延后。所以说我们会 将js放在背面,以削减First Paint的时候,然则不会削减DOMContentLoaded被触发的时候

    本节参考文章:DOMContentLoaded与load的区分

    10.clientheight / offsetheight

    clientheight:内容的可视地区,不包含border。clientheight=padding+height-横向转动轴高度。

    《前端基本功-罕见观点(三)》

    这里写图片形貌

    offsetheight,它包含padding、border、横向转动轴高度。

    offsetheight=padding+height+border+横向转动轴高度

    《前端基本功-罕见观点(三)》

    scrollheight,可转动高度,就是将转动框拉直,不再转动的高度,这个很好明白。 It includes the element’s padding, but not its border or margin.

    《前端基本功-罕见观点(三)》

    本节参考文章:css clientheight、offsetheight、scrollheight详解

    11.use strict 有什么意义和优点

    1. 使调试越发轻易。那些被疏忽或默默失利了的代码毛病,会发作毛病或抛出异常,因而尽早提示你代码中的题目,你才更快地指引到它们的源代码。
    2. 防备不测的全局变量。如果没有严厉形式,将值分配给一个未声明的变量会自动建立该称号的全局变量。这是JavaScript中最罕见的毛病之一。在严厉形式下,如许做的话会抛出毛病。
    3. 消弭 this 强迫。如果没有严厉形式,援用null或未定义的值到 this 值会自动强迫到全局变量。这可以会致使很多使人头痛的题目和让人巴不得拔本身头发的bug。在严厉形式下,援用 null或未定义的 this 值会抛出毛病。
    4. 不允许反复的属性称号或参数值。当检测到对象中反复定名的属性,比方:

      var object = {foo: "bar", foo: "baz"};)

      或检测到函数中反复定名的参数时,比方:

      function foo(val1, val2, val1){})

      严厉形式会抛出毛病,因而捕获险些可以肯定是代码中的bug可以防备糟蹋大批的跟踪时候。

    5. 使 eval() 更平安。在严厉形式和非严厉形式下, eval() 的行动体式格局有所差异。最不言而喻的是,在严厉形式下,变量和声明在 eval() 语句内部的函数不会在包含局限内建立(它们会在非严厉形式下的包含局限中被建立,这也是一个罕见的题目源)。
    6. 在 delete 运用无效时抛出毛病。 delete 操纵符(用于从对象中删除属性)不能用在对象不可设置的属性上。当试图删除一个不可设置的属性时,非严厉代码将默默地失利,而严厉形式将在如许的状况下抛出异常。

    本节参考文章:典范口试题(4)

    12.罕见 JavaScript 内存走漏

    1. 不测的全局变量

    JavaScript 处置惩罚未定义变量的体式格局比较宽松:未定义的变量会在全局对象建立一个新变量。在阅读器中,全局对象是 window 。

    function foo(arg) {
        bar = "this is a hidden global variable";
    }
    原形是:
    
    ```
    function foo(arg) {
        window.bar = "this is an explicit global variable";
    }
    ```
    函数 foo 内部忘记运用 var ,不测建立了一个全局变量。此例走漏了一个简朴的字符串,无伤大雅,然则有更糟的状况。
    
    另一种不测的全局变量可以由 this 建立:
    
    ```
    function foo() {
        this.variable = "potential accidental global";
    }
    // Foo 挪用本身,this 指向了全局对象(window)
    // 而不是 undefined
    foo();
    ```
    在 JavaScript 文件头部加上 'use strict',可以防备此类毛病发作。启用严厉形式剖析 JavaScript ,防备不测的全局变量。
    
    1. 被忘记的计时器或回调函数
      在 JavaScript 中运用 setInterval 异常寻常。一段罕见的代码:

      var someResource = getData();
      setInterval(function() {
          var node = document.getElementById('Node');
          if(node) {
              // 处置惩罚 node 和 someResource
              node.innerHTML = JSON.stringify(someResource));
          }
      }, 1000);

      此例说清楚明了什么:与节点或数据关联的计时器不再须要,node 对象可以删除,悉数回调函数也不须要了。但是,计时器回调函数依然没被接纳(计时器住手才会被接纳)。同时,someResource 如果存储了大批的数据,也是没法被接纳的。

      关于观察者的例子,一旦它们不再须要(或许关联的对象变成不可达),明白地移除它们异常主要。老的 IE 6 是没法处置惩罚轮回援用的。现在,纵然没有明白移除它们,一旦观察者对象变成不可达,大部份阅读器是可以接纳观察者处置惩罚函数的。

      观察者代码示例:

      var element = document.getElementById('button');
      function onClick(event) {
          element.innerHTML = 'text';
      }
      element.addEventListener('click', onClick);

      对象观察者和轮回援用注重事项

      老版本的 IE 是没法检测 DOM 节点与 JavaScript 代码之间的轮回援用,会致使内存走漏。现在,当代的阅读器(包含 IE 和 Microsoft Edge)运用了更先进的渣滓接纳算法,已可以准确检测和处置惩罚轮回援用了。换言之,接纳节点内存时,没必要非要挪用 removeEventListener 了。

    2. 脱离 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 接纳。
      }

      别的还要斟酌 DOM 树内部或子节点的援用题目。如果你的 JavaScript 代码中保留了表格某一个 <td> 的援用。未来决议删除悉数表格的时刻,直觉以为 GC 会接纳除了已保留的 <td> 之外的别的节点。现实状况并非如此:此 <td> 是表格的子节点,子元素与父元素是援用关联。因为代码保留了 <td> 的援用,致使悉数表格仍待在内存中。保留 DOM 元素援用的时刻,要小心谨慎。

    3. 闭包

    闭包是 JavaScript 开辟的一个症结方面:匿名函数可以接见父级作用域的变量。

    防备滥用

    本节参考文章:4类 JavaScript 内存走漏及怎样防备

    13.援用计数 / 标记消灭

    js渣滓接纳有两种罕见的算法:援用计数和标记消灭。

    • 援用计数就是跟踪对象被援用的次数,当一个对象的援用计数为0即没有其他对象援用它时,申明该对象已无需接见了,因而就会接纳其所占的内存,如许,当渣滓接纳器下次运转就会开释援用数为0的对象所占用的内存。
    • 标记消灭法是当代阅读器经常使用的一种渣滓网络体式格局,当变量进入环境(即在一个函数中声明一个变量)时,就将此变量标记为“进入环境”,进入环境的变量是不能被开释,因为只要实行流进入响应的环境,就可以会援用它们。而当变量脱离环境时,就标记为“脱离环境”。

      渣滓网络器在运转时会给贮存在内存中的一切变量加上标记,然后会去掉环境中的变量以及被环境中的变量援用的变量的标记,当实行终了那些没有存在援用 没法接见的变量就被加上标记,末了渣滓网络器完成消灭事情,开释掉那些打上标记的变量所占的内存。

     function problem() {
        var A = {};
        var B = {};
        A.a = B;
        B.a = A;
    }

    援用计数存在一个弊病就是轮回援用题目(上边)

    标记消灭不存在轮回援用的题目,是因为当函数实行终了以后,对象A和B就已脱离了地点的作用域,此时两个变量被标记为“脱离环境”,守候被渣滓网络器接纳,末了开释其内存。

    剖析以下代码:

        function createPerson(name){
            var localPerson = new Object();
            localPerson.name = name;
            return localPerson;
        }
        var globalPerson = createPerson("Junga");
        globalPerson = null;//手动消除全局变量的援用

    在这个🌰中,变量globalPerson取得了createPerson()函数的返回的值。在createPerson()的内部建立了一个局部变量localPerson并增添了一个name属性。因为localPerson在函数实行终了以后就脱离实行环境,因而会自动消除援用,而关于全局变量来讲则须要我们手动设置null,消除援用。

    不过,消除一个值的援用并不意味着自动接纳该值所占用的内存,消除援用真正的作用是让值脱离实行环境,以便渣滓网络器下次运转时将其收回。

    本节参考文章:JavaScript的内存题目

    14.前后端路由差异

    • 1.后端每次路由要求都是从新接见服务器
    • 2.前端路由现实上只是JS依据URL来操纵DOM元素,依据每一个页面须要的去服务端要求数据,返回数据后和模板举行组合。

    本节参考文章:2018前端口试总结…

    15.window.history / location.hash

    一般 SPA 中前端路由有2种完成体式格局:

    • window.history
    • location.hash

    下面就来引见下这两种体式格局详细怎样完成的

    一.history

    1.history基础引见

    window.history 对象包含阅读器的汗青,window.history 对象在编写时可不运用 window 这个前缀。history是完成SPA前端路由是一种主流要领,它有几个原始要领:

    • history.back() – 与在阅读器点击退却按钮雷同
    • history.forward() – 与在阅读器中点击按钮向前雷同
    • history.go(n) – 接收一个整数作为参数,挪动到该整数指定的页面,比方go(1)相称于forward(),go(-1)相称于back(),go(0)相称于革新当前页面
    • 如果挪动的位置超出了接见汗青的边境,以上三个要领并不报错,而是寂静失利

    在HTML5,history对象提出了 pushState() 要领和 replaceState() 要领,这两个要领可以用来向汗青栈中增添数据,就好像 url 变化了一样(过去只要 url 变化汗青栈才会变化),如许就可以很好的模拟阅读汗青和行进退却了,现在的前端路由也是基于这个道理完成的。

    2.history.pushState

    pushState(stateObj, title, url) 要领向汗青栈中写入数据,其第一个参数是要写入的数据对象(不大于640kB),第二个参数是页面的 title, 第三个参数是 url (相对途径)。

    • stateObj :一个与指定网址相干的状况对象,popstate事宜触发时,该对象会传入回调函数。如果不须要这个对象,此处可以填null。
    • title:新页面的题目,然则一切阅读器现在都疏忽这个值,因而这里可以填null。
    • url:新的网址,必需与当前页面处在同一个域。阅读器的地点栏将显现这个网址。

    关于pushState,有几个值得注重的处所:

    • pushState要领不会触发页面革新,只是致使history对象发作变化,地点栏会有回响反映,只要当触发行进退却等事宜(back()和forward()等)时阅读器才会革新
    • 这里的 url 是遭到同源战略限定的,防备歹意剧本模拟其他网站 url 用来诳骗用户,所以当违犯同源战略时将会报错

    3.history.replaceState

    replaceState(stateObj, title, url) 和pushState的区分就在于它不是写入而是替代修正阅读汗青中当前记载,其他和 pushState如出一辙

    4.popstate事宜

    • 定义:每当同一个文档的阅读汗青(即history对象)涌现变化时,就会触发popstate事宜。
    • 注重:仅仅挪用pushState要领或replaceState要领 ,并不会触发该事宜,只要用户点击阅读器倒退按钮和行进按钮,或许运用JavaScript挪用back、forward、go要领时才会触发。别的,该事宜只针对同一个文档,如果阅读汗青的切换,致使加载差异的文档,该事宜也不会触发。
    • 用法:运用的时刻,可以为popstate事宜指定回调函数。这个回调函数的参数是一个event事宜对象,它的state属性指向pushState和replaceState要领为当前URL所供应的状况对象(即这两个要领的第一个参数)。

    5.history完成spa前端路由代码

    <a class="api a">a.html</a>
    <a class="api b">b.html</a>
     // 注册路由
        document.querySelectorAll('.api').forEach(item => {
          item.addEventListener('click', e => {
            e.preventDefault();
            let link = item.textContent;
            if (!!(window.history && history.pushState)) {
              // 支撑History API
              window.history.pushState({name: 'api'}, link, link);
            } else {
              // 不支撑,可运用一些Polyfill库来完成
            }
          }, false)
        });
    
        // 监听路由
        window.addEventListener('popstate', e => {
          console.log({
            location: location.href,
            state: e.state
          })
        }, false)

    popstate监听函数里打印的e.state就是history.pushState()里传入的第一个参数,在这里即为{name: ‘api’}

    二.Hash

    1.Hash基础引见

    url 中可以带有一个 hash http://localhost:9000/#/a.html

    window 对象中有一个事宜是 onhashchange,以下几种状况都邑触发这个事宜:

    • 直接变动阅读器地点,在最背面增添或转变#hash;
    • 经由过程转变location.href或location.hash的值;
    • 经由过程触发点击带锚点的链接;
    • 阅读器行进退却可以致使hash的变化,条件是两个网页地点中的hash值差异。

    2.Hash完成spa前端路由代码

        // 注册路由
        document.querySelectorAll('.api').forEach(item => {
          item.addEventListener('click', e => {
            e.preventDefault();
            let link = item.textContent;
            location.hash = link;
          }, false)
        });
    
        // 监听路由
        window.addEventListener('hashchange', e => {
          console.log({
            location: location.href,
            hash: location.hash
          })
        }, false)

    本节参考文章:vue 单页运用(spa)前端路由完成道理

        原文作者:于梦中2010
        原文地址: https://segmentfault.com/a/1190000017875120
        本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
    点赞