(转)JavaScript内存模型
1. 什么是JS对象?
在JS中,对象是一组无序属性的集合。其中,属性可以是基本数据类型、引用类型、函数。如下面这个对象的例子:
var chai={
name : "柴毛毛", // 属性为基本数据类型
perosn : { // 属性为引用类型
address : "xxx",
sex : "man"
},
getName : function () { // 属性为函数
return this.name;
}
}
也就是说,JS中的对象类似于Java中的Map,由键值对构成;其中键是字符串类型的属性名,值可以为上述三种类型中的任意类型。
2. 如何创建JS对象?
JS中创建对象的方法有很多,各有千秋。这篇博客主要介绍对象创建过程中的内存模型,因此只介绍通过构造函数创建对象的方法,其余方法期待下一篇博客吧。
2.1. 通过构造函数创建对象的步骤
- 定义构造函数
function Person (name,age) {
this.name = name;
this.age = age;
this.getName = function(){
return name;
}
}
- 通过new创建对象
var person1 = new Person('柴毛毛',18);
通过上述两步后,就能创建一个Person对象,并由变量person1指向,通过该变量就能访问这个对象的所有属性。
构造函数与普通函数的区别?
3. 对象创建过程的内存模型
以上的概念较为基础,点到为止,为下面讲解对象的内存模型作铺垫,OK,重头戏来了。
3.1. 初始化构造函数
我们知道,构造函数也是函数,所有函数在被执行前都需要被初始化,函数初始化也就是创建该函数对象的过程。那么函数什么时候会被初始化呢?
当构造函数所在的外层函数被执行时,JS引擎会为该外层函数创建一个执行环境,并压入执行环境栈中。这个过程称为函数执行环境的准备阶段。
若构造函数以“函数声明”的方式定义,那么构造函数的初始化在外层函数的环境准备阶段完成;
若构造函数是以“函数表达式”的方式定义,那么只有当JS引擎执行到这一行代码的时候才会初始化构造函数。
我们知道,JS中的函数就是一个对象,因此函数的初始化过程其实就是创建了一个函数对象。
PS:不仅仅是构造函数,所有函数初始化发生的时机都是如此。
综上所述:
函数初始化的时机与函数所在的外层函数有关,也与函数的定义方式有关。
若函数以“函数声明”的形式定义,那么该函数的初始化在其外层函数执行前(即外层函数执行环境的准备阶段)完成;
若函数以函数表达式的形式定义,那么该函数的初始化在其外层函数执行到这行代码的时候完成。
3.2. 创建构造函数的原型对象
当构造函数的对象创建完成后,JS引擎随之会为该对象创建一个原型对象。
原型对象默认只有一个属性“construcor”,指向它的构造函数对象。
而每个函数对象默认都有一个prototype属性,该属性指向它的原型对象。
此时,内存中有如下对象:
PS:不仅仅是构造函数,所有函数初始化完成之后在内存中都有上述结构。
所有函数初始化完成后都会创建一个函数对象和一个原型对象,并且通过prototype、construcor属性相互引用。
3.3. 执行关键字new
var person1 = new Person('柴毛毛',18);
当通过关键字new创建一个对象时,JS引擎会做如下几件事:
- 创建一个对象
就像这样:
var object = new Object();
这个object对象将会有一个指向原型对象的属性,它是一个隐式属性,我们无法通过JS代码访问,但JS引擎可以访问。
大多数浏览器将它定义为“proto”。
此时内存中一共存在三个对象,分别是:构造函数本身的函数对象、构造函数的原型对象、构造函数的实例对象。它们之间通过prototype、proto、constructor指针相互引用。
- 准备构造函数的执行环境(变量对象、作用域链、执行环境)
对象创建完后,就要开始执行构造函数中的代码。但在执行函数之前,首先需要准备函数的执行环境:
(本过程详解请参阅:稳扎稳打JavaScript——作用域链)
* 创建构造函数的变量对象(用于存储函数执行过程中的所有变量,包括this和arguments)
* 创建构造函数的作用域,压入作用域链的头部,并且指向刚才创建的变量对象;(函数的作用域链在函数初始化时就已创建)
* 创建构造函数的执行环境,并指向该函数的作用域链
此时内存模型如下:
到此为止,内存中存在两大块内容,一块是用于执行构造函数的“构造函数执行环境”,它包括:构造函数的执行环境、构造函数的作用域链、构造函数的变量对象。
另一块内存空间存储了与创建对象相关的内容,包括:构造函数本身的函数对象、构造函数的原型对象、构造函数的实例对象。
但此时这两块内存间并没有联系,接下来this就要出场了。
- 将变量对象中的this指向实例对象
此时,构造函数的变量对象中的this指向了构造函数对象,从而使两大块内存之间建立起了桥梁,此时的内存图如下: - 执行构造函数中的代码
function Person (name,age) {
this.name = name;
this.age = age;
this.getName = function(){
return name;
}
}
此时,JS引擎将会逐行执行构造函数中的代码。
比如,当代码执行到this.name=name时,JS引擎首先会在当前作用域链中寻找变量this。我们知道,查找变量首先从作用域的头部开始,因此首先寻找下标为0的变量对象;在该变量对象中找到了this,因此查找成功,紧接着在this指向的对象中创建name属性,并赋上局部变量name的值。
当构造函数中的代码执行完毕,内存模型如下:
构造函数创建的那个对象中多出了几个属性。
- 返回刚创建的对象
最后,将this指向的那个对象返回。
4. 变量查找 与 属性查找 的区别
先来复习下JS的变量查找过程(本过程详解请参阅:稳扎稳打JavaScript——作用域链)。
仍以上述代码为例:
function Person (name,age) {
this.name = name;
this.age = age;
this.getName = function(){
return name;
}
}
var person1 = new Person('柴毛毛',18);
4.1. 变量查找(作用域查找)
我们知道,通过构造函数创建一个对象时,会发生以下几件事:
1. 创建一个object对象
2. 准备构造函数的执行环境
3. 将构造函数的this指向object对象
4. 执行构造函数
5. 返回object对象
也就是说,通过new创建一个对象本质上仍然是把构造函数当作普通函数执行,只不过在构造函数执行前增加了一句var object=new Object();并且在构造函数执行结束后返回这个object;其余过程就是在执行一个普通函数。
那么在函数执行过程中,如果需要用到局部变量,就会发生变量查找。
当执行代码“this.name = name”时,这句代码涉及到两个局部变量:this、name。
以查找this为例,JS引擎会沿着当前函数的作用域链依次查找变量对象。若在某一个作用域指向的变量对象中找到this,则查找成功;否则就继续沿着作用域链向后查找。
4.2. 属性查找(原型链查找)
变量查找是在作用域链上进行,而属性查找是在原型链上进行的。
继续上述例子。当JS引擎在某个变量对象中找到this后,变量查找的过程结束,若查找的是一个基本数据类型的变量,那么查找结束;若查找的变量是一个对象,并且需要对该对象的属性进行操作,那么接下来就要进入属性查找的过程。
我们知道,构造函数执行前会将它的this指向构造函数的实例对象,因此,当执行“this.name”时,JS引擎就会在this指向的实例对象中查找。若在this指向的实例对象中查找不到,就会继续查找该实例对象proto属性指向的原型对象,若该原型对象中没有,则继续查找原型对象的原型对象,直到查找成功或找到Object的原型对象为止。
5. this是在函数执行时赋值的
(转)JavaScript内存模型的更多相关文章
- javascript 内存模型
我对于 JavaScript 的内存模型一直都比较困惑,很想了解在操作变量的时候,JS 是如何工作的.如果你和我有同样的困惑,希望这篇文章能给你一些启发. 译文,喜欢原文的可以直接拉到底部 当我们声明 ...
- JavaScript 是如何工作的:JavaScript 的内存模型
摘要: 从内存角度理解 let 和 const 的意义. 原文:JavaScript 是如何工作的:JavaScript 的内存模型 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 这 ...
- JavaScript学习系列之内存模型篇
一个热爱技术的菜鸟...用点滴的积累铸就明日的达人 正文 如果真的想学好一门语言,那么一定要了解它内存模型,本篇文章就带你走进JavaScript的内存模型,由于本人才疏学浅,若有什么表述有误的地方, ...
- JavaScript的内存模型
引言 在我们的前端日常工作中,无时无刻不在进行着变量的声明和赋值,你是否也曾碰到过变量声明报错或变量被污染的问题,如果你跟笔者一样碰到过,那么我们应该暂时停下来好好思考问题发生的原因以及如何采取相应的 ...
- 浅谈JavaScript原型图与内存模型
js原型详解 1.内存模型: 1.原型是js中非常特殊一个对象,当一个函数(Person)创建之后,会随之就产生一个原型对象 2. 当通过这个函数的构造函数创建了一个具体的对象(p1)之后,在这个具体 ...
- JavaScript闭包模型
JavaScript闭包模型 ----- [原创翻译]2016-09-01 09:32:22 < 一> 闭包并不神秘 本文利用JavaScript代码来阐述闭包,目的是为了使普通 ...
- JavaScript事件模型及事件代理
事件模型 JavaScript事件使得网页具备互动和交互性,我们应该对其深入了解以便开发工作,在各式各样的浏览器中,JavaScript事件模型主要分为3种:原始事件模型.DOM2事件模型.IE事件模 ...
- JavaScript 内存机制
简介 每种编程语言都有它的内存管理机制,比如简单的C有低级的内存管理基元,像malloc(),free().同样我们在学习JavaScript的时候,很有必要了解JavaScript的内存管理机制. ...
- [书籍翻译] 《JavaScript并发编程》 第二章 JavaScript运行模型
本文是我翻译<JavaScript Concurrency>书籍的第二章 JavaScript运行模型,该书主要以Promises.Generator.Web workers等技术来讲解J ...
随机推荐
- 【VBA研究】浮点数计算总是有误差的
作者:iamlaosong 数字有两种表达方式.一种是整数,一种是浮点数.浮点数是属于有理数中某特定子集的数的数字表示,在计算机中用以近似表示随意某个实数.详细的说,这个实数由一个整数或定点数(即尾数 ...
- malefile
什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作,但我觉得要作一个好的和professional的程序员,makefile还是要 ...
- ThinkPHP CURD方法中field方法详解
导读:ThinkPHP CURD方法的field方法属于模型的连贯操作方法之一,主要目的是标识要返回或者操作的字段,可以用于查询和写入操作. 1.用于查询在查询操作中field方法是使用最频繁的.$M ...
- SVN 版本服务器搭配全过程详解(含服务端、客户端)
1.为什么要用VisualSVN Server,而不用Subversion? 回答: 因为如果直接使用Subversion,那么在Windows 系统上,要想让它随系统启动,就要封装SVN Serve ...
- 锁与theadLocal的合并使用
); , TimeUnit.); ){ , TimeUnit.); ){ ); infoPO.setRemainAmount(infoPO.getRemainAmount()-amount); bas ...
- c# 创建socket客户端
c# 创建socket客户端 using System; using System.Collections.Generic; using System.Linq; using System.Text; ...
- 【ExtJS】各种获取元素组件方法
1.get().getDom().getCmp().getBody().getDoc(): get(id/obj): get方法用来得到一个Ext元素,也就是类型为Ext.Element的对象, Ex ...
- [posix]Posix多线程编程
就不排版了,可以到原作者博客下看 转自:http://www.cnblogs.com/zhangsf/archive/2013/09/09/3309867.html 目录表 摘要 译者序 Pthre ...
- sqlserver日期函数<转>
一.sql server日期时间函数Sql Server中的日期与时间函数 1. 当前系统日期.时间 select getdate() 2. dateadd 在向指定日期加上一段时间的基 ...
- 一站式学习Wireshark(二):应用Wireshark观察基本网络协议
TCP: TCP/IP通过三次握手建立一个连接.这一过程中的三种报文是:SYN,SYN/ACK,ACK. 第一步是找到PC发送到网络服务器的第一个SYN报文,这标识了TCP三次握手的开始. 如果你找不 ...