资讯专栏INFORMATION COLUMN

前端基本功-常见概念(三)

Steven / 837人阅读

摘要:前端基本功常见概念一点这里前端基本功常见概念二点这里前端基本功常见概念三点这里超文本标记语言,显示信息,不区分大小写升级版的,区分大小写可扩展标记语言被用来传输和存储数据规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。

前端基本功-常见概念(一) 点这里
前端基本功-常见概念(二) 点这里
前端基本功-常见概念(三) 点这里

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 **/
    
    
    
    /** 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

构造函数相比工厂模式:

没有集中实例化

没有返回对象实例

直接将属性和方法赋值给this

解决了对象实例归属问题

构造函数编写规范:

构造函数也是函数,但是函数名的第一个字母大写

必须使用new运算符 + 函数名(首字母大写)例如:var box = new Create();

构造函数和普通函数的区别:

普通函数,首字母无需大写

构造函数,用普通函数调用方式无效

查看归属问题,要创建两个构造函数:

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文档加载的步骤为:

解析HTML结构。

DOM树构建完成。//DOMContentLoaded

加载外部脚本和样式表文件。

解析并执行脚本代码。

加载图片等外部文件。

页面加载完毕。//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 有什么意义和好处

使调试更加容易。那些被忽略或默默失败了的代码错误,会产生错误或抛出异常,因此尽早提醒你代码中的问题,你才能更快地指引到它们的源代码。

防止意外的全局变量。如果没有严格模式,将值分配给一个未声明的变量会自动创建该名称的全局变量。这是JavaScript中最常见的错误之一。在严格模式下,这样做的话会抛出错误。

消除 this 强制。如果没有严格模式,引用null或未定义的值到 this 值会自动强制到全局变量。这可能会导致许多令人头痛的问题和让人恨不得拔自己头发的bug。在严格模式下,引用 null或未定义的 this 值会抛出错误。

不允许重复的属性名称或参数值。当检测到对象中重复命名的属性,例如:

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

或检测到函数中重复命名的参数时,例如:

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

严格模式会抛出错误,因此捕捉几乎可以肯定是代码中的bug可以避免浪费大量的跟踪时间。

使 eval() 更安全。在严格模式和非严格模式下, eval() 的行为方式有所不同。最显而易见的是,在严格模式下,变量和声明在 eval() 语句内部的函数不会在包含范围内创建(它们会在非严格模式下的包含范围中被创建,这也是一个常见的问题源)。

在 delete 使用无效时抛出错误。 delete 操作符(用于从对象中删除属性)不能用在对象不可配置的属性上。当试图删除一个不可配置的属性时,非严格代码将默默地失败,而严格模式将在这样的情况下抛出异常。

本节参考文章:经典面试题(4)

12.常见 JavaScript 内存泄漏

意外的全局变量

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 ,避免意外的全局变量。

被遗忘的计时器或回调函数
在 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 了。

脱离 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 代码中保存了表格某一个 的引用。将来决定删除整个表格的时候,直觉认为 GC 会回收除了已保存的 以外的其它节点。实际情况并非如此:此 是表格的子节点,子元素与父元素是引用关系。由于代码保留了 的引用,导致整个表格仍待在内存中。保存 DOM 元素引用的时候,要小心谨慎。

闭包

闭包是 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;//手动解除全局变量的引用

在这个

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/100981.html

相关文章

  • 前端本功-常见概念(一)

    摘要:前端基本功常见概念一点这里前端基本功常见概念二点这里前端基本功常见概念三点这里什么是原型链当一个引用类型继承另一个引用类型的属性和方法时候就会产生一个原型链。函数式编程是声明式而不是命令式,并且应用程序状态通过纯函数流转。 前端基本功-常见概念(一) 点这里前端基本功-常见概念(二) 点这里前端基本功-常见概念(三) 点这里 1.什么是原型链 当一个引用类型继承另一个引用类型的属性和方...

    bladefury 评论0 收藏0
  • 前端本功-常见概念()

    摘要:前端基本功常见概念一点这里前端基本功常见概念二点这里前端基本功常见概念三点这里超文本标记语言,显示信息,不区分大小写升级版的,区分大小写可扩展标记语言被用来传输和存储数据规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。 前端基本功-常见概念(一) 点这里前端基本功-常见概念(二) 点这里前端基本功-常见概念(三) 点这里 1.HTML / XML / XHTML html...

    happen 评论0 收藏0
  • 前端2018现在上车还还得及么

    摘要:面向对象三大特征继承性多态性封装性接口。第五阶段封装一个属于自己的框架框架封装基础事件流冒泡捕获事件对象事件框架选择框架。核心模块和对象全局对象,,,事件驱动,事件发射器加密解密,路径操作,序列化和反序列化文件流操作服务端与客户端。 第一阶段: HTML+CSS:HTML进阶、CSS进阶、div+css布局、HTML+css整站开发、 JavaScript基础:Js基础教程、js内置对...

    stormgens 评论0 收藏0
  • 前端2018现在上车还还得及么

    摘要:面向对象三大特征继承性多态性封装性接口。第五阶段封装一个属于自己的框架框架封装基础事件流冒泡捕获事件对象事件框架选择框架。核心模块和对象全局对象,,,事件驱动,事件发射器加密解密,路径操作,序列化和反序列化文件流操作服务端与客户端。 第一阶段: HTML+CSS:HTML进阶、CSS进阶、div+css布局、HTML+css整站开发、 JavaScript基础:Js基础教程、js内置对...

    mylxsw 评论0 收藏0

发表评论

0条评论

Steven

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<