JavaScript对象 、堆与栈
JavaScript对象 、堆与栈
- 堆与栈
- JavaScript 对象
- 对象的分类
- 创建一个对象
- 对象属性
- 访问对象属性
- 修改对象的属性值;
- 对象方法
- 访问对象方法
- 移除对象的属性
- 检查属性是否存在
- 计算属性
- 保留字段可以用作属性名
- for…in 循环遍历对象
- 像对象一样排序
- 引用复制
- 比较引用
- 常量对象
- 复制和合并,Object.assign
- 总结
堆与栈
栈:原始数据类型(Undefined,Null,Boolean,Number、String)
堆:引用数据类型(对象、数组和函数)两种类型的区别是: 区别:基础数据类型的数据存储在栈中,变量直接指向的是基础数据类型的值。引用数据类型的数据存储在堆中,变量指向的是引用数据类型的地址。比较:基本数据类型比较时,比较值。而引用数据类型比较时,比较内存地址,如果内存地址相同,指向了同一个对象,则相等,否则不相等。
原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其
在栈中的地址,取得地址后从堆中获得实体
JavaScript 对象
对象是指一个具体的事物。万物皆对象。
一个具体的事物一般都会有行为和特征。
对象可以通过花括号 {…}
和其中包含一些可选的属性来创建。属性是一个键值对,键是一个字符串(也叫做属性名),值可以是任何类型。
我们可以把对象想象成存放文件的橱柜。文件按照他们的名字来排列。这样根据文件名我们就很容易找到、添加或删除一个文件了。
对象的分类
内建对象
由ES标准定义的对象,在任何ES实现中都可以使用 ,
js提供的对象 Math String Number Boolean Function Object
宿主对象
主要有浏览器提供的对象。
BOM DOM
- 自定义对象
开发人员自己定义的对象。
创建一个对象
我们可以用下面两种语法的任一种来创建一个空的对象(“空柜子”):
let user = new Object(); // “构造函数” 的语法
let user = {}; // “字面量” 的语法
通常,我们用花括号。这种方式我们叫做字面量。
对象属性
可以说 “JavaScript 对象是变量的容器”。
但是,我们通常认为 “JavaScript 对象是键值对的容器”。
键值对通常写法为 name : value (键与值以冒号分割)。
键值对在 JavaScript 对象通常称为 对象属性。
我们可以在创建的时候立即给对象一些属性,在 {...}
里面放置一些键值对。
let user = { // 一个对象name: "John", // 键 "name",值 "John"age: 30 // 键 "age",值 30
};
属性有键(或者也可以叫做名字,标识符),在冒号的前面 ":"
,值在冒号的右边。
在 user
对象中, 有两个属性:
- 第一个的键是
"name"
值是"John"
。 - 第二个的键是
"age"
值是30
。
是一个陈列着标记有两个 “name” 和 “age” 签名文件的橱柜。
访问对象属性
- 对象名.属性名
// 读取文件的属性:
alert( user.name ); // John
alert( user.age ); // 30
- 对象名[‘属性名’]
person["lastName"];
修改对象的属性值;
- 对象名.属性=值;
man.age = 222;
对象名[‘属性名’]=值
man['age'] = 222
对象方法
对象的方法定义了一个函数,并作为对象的属性存储。
对象方法通过添加 () 调用 (作为一个函数)。
你可以使用以下语法创建对象方法:
skill : function() {// 代码
}
访问对象方法
hero.skill();
移除对象的属性
移除一个属性,我们用 delete
操作:
delete user.age;
检查属性是否存在
console.log( key in obj ) //返回一个布尔值,key是属性名 obj对象名
计算属性
我们可以在对象字面量中使用方括号。这叫做计算属性。
例如:
let fruit = prompt("Which fruit to buy?", "apple");let bag = {[fruit]: 5, // 属性名从 fruit 变量中计算
};alert( bag.apple ); // 5 如果 fruit="apple"
计算属性的含义很简单:[fruit]
含义是这个值从 fruit
变量中获取。
所以,如果一个人输入 "apple"
,bag
将是 {apple: 5}
。
本质上,这跟下面的语法相同:
let fruit = prompt("Which fruit to buy?", "apple");
let bag = {};// 从 fruit 变量中获取值
bag[fruit] = 5;
…但是看起来好多了。
我们在方括号中可以用更复杂的表达式:
let fruit = 'apple';
let bag = {[fruit + 'Computers']: 5 // bag.appleComputers = 5
};
方括号比点符号更强大。它允许任何属性名和变量,但写起来也更加麻烦。
大部分时间里,当属性名是已知且简单的时候,用点方法。如果有一些复杂的操作,那么就用方括号。
保留字段可以用作属性名
变量名不能用保留字段,像:“for”, “let”, “return” 等。
对于对象的属性,没有这些限制,都可以的:
let obj = {for: 1,let: 2,return: 3
}alert( obj.for + obj.let + obj.return ); // 6
基本上,什么都可以,只有一个特殊的:"__proto__"
因为历史原因要特别对待。比如,我们不能把它设置为非对象的值:
let obj = {};
obj.__proto__ = 5;
alert(obj.__proto__); // [object Object],这样不行
我们从代码中可以看出来,把它赋值成 5
被忽略了。
如果我们蓄意去存储随机的键值对或者允许一个访问者去指定键,那可能就会产生很多 bug 并且使对象变得危险。
比如,访问者可能选择 “proto” 作为键,这个赋值的逻辑就失败了(像上面那样)。
有一种让对象把 __proto__
作为属性的方法,在后面章节会讲到,现在我们先来学习对象的更多知识。 还有另外一种数据结构 Map,我们会在后面章节学到,它支持任意的键值。
for…in 循环遍历对象
为了使用对象所有的属性,就可以利用 for..in
循环。这跟 for(;;)
是完全不一样的东西。
语法:
for(key in object) {// 各个属性键值的执行区
}
例如,我们列出 user
所有的属性值:
let user = {name: "John",age: 30,isAdmin: true
};for(let key in user) {// keysalert( key ); // name, age, isAdmin// 属性键的值alert( user[key] ); // John, 30, true
}
注意,所有的 “for” 都允许我们在循环中定义变量,像 let key
这样。
同样,我们可以用其他属性名来代替 key
。例如 "for(let prop in obj)"
也很常用。
像对象一样排序
对象有顺序吗?换句话说,如果我们遍历一个对象,我们会按照赋值属性的顺序来获得属性吗?这靠谱吗?
简短的回答是:”有特别的顺序“:整数属性有顺序,其他是按照创建的顺序,细节如下:
例如,让我们考虑一个带有电话号码的对象:
let codes = {"49": "Germany","41": "Switzerland","44": "Great Britain",// ..,"1": "USA"
};for(let code in codes) {alert(code); // 1, 41, 44, 49
}
对象可用于向用户建议选项列表。如果我们的网站主要面向德国用户,可能想让 49
来当做第一个。
然而如果我们执行代码,会看到完全不同的景象:
- USA (1) 在最前面
- 然后是 Switzerland (41) 以及其它内容
因为这些电话号码是整数,所以它们以升序来排列。所以我们看到的是 1, 41, 44, 49
。
整数属性?那是什么?
这里的“整数属性”术语指的是一个字符串,可以在不改变的情况下对整数进行转换。
所以,“49” 是一个整数属性名,因为我们把它转换成整数,再转换回来,它还是一样。但是 “+49” 和 “1.2” 就不行了:
// Math.trunc 是内置的去除小数点的方法。
alert( String(Math.trunc(Number("49"))) ); // "49",同样,整数属性
alert( String(Math.trunc(Number("+49"))) ); // "49",不同于 "+49" ⇒ 不是整数属性
alert( String(Math.trunc(Number("1.2"))) ); // "1",不同于 "1.2" ⇒ 不是整数属性
…另外一边,如果属性名不是整数,那它们就按照创建时候的顺序来排序:
let user = {name: "John",surname: "Smith"
};
user.age = 25; // 增加一个// 非整数属性是按照创建的顺序来排列的。
for (let prop in user) {alert( prop ); // name, surname, age
}
所以,这就解决了电话号码的问题,我们把整数属性转换成非整数的,在前面增加一个 "+"
就行了。
像这样:
let codes = {"+49": "Germany","+41": "Switzerland","+44": "Great Britain",// ..,"+1": "USA"
};for(let code in codes) {alert( +code ); // 49, 41, 44, 1
}
现在跟预想的一样了。
引用复制
对象和其他原始的类型相比有一个很重要的区别,对象都是按引用存储复制的。
原始类型是:字符串,数字,布尔类型 – 是被整个赋值的。
例如:
let message = "Hello!";
let phrase = message;
结果是我们得到了不同的值,每个存的都是 "Hello!"
。
对象跟这个不一样。
变量存储的不是对象本身,而是对象的“内存地址”,是对象的引用。
下面是对象的存储结构图:
let user = {name: "John"
};
在这里,对象存在内存里面。user
有一个对它的引用。
当对象被复制的时候 – 引用被复制了一份, 对象并没有被复制。
我们想象对象是一个抽屉,变量是一个钥匙,拷贝对象复制了钥匙,但是并没有复制抽屉本身。
例如:
let user = { name: "John" };let admin = user; // 复制引用
现在我们有了两个变量,但是都指向同一个对象:
我们可以用任何变量去获取抽屉内容,改变它的内容:
let user = { name: 'John' };let admin = user;admin.name = 'Pete'; // 改变 "admin" 的引用alert(user.name); // 'Pete', changes are seen from the "user" reference
上面的例子展示了只存在一个对象,就像我们的抽屉有两把钥匙,如果一个钥匙(admin
)去使用了抽屉,稍后使用另外一个钥匙(user
)打开的时候,就会看到有变化。
比较引用
等号 ==
和严格等 ===
对于对象来说没差别。
当两个引用指向同一个对象的时候他们相等。
例如,两个引用指向同一个对象,他们相等:
let a = {};
let b = a; // 复制引用alert( a == b ); // true,两个变量指向同一个对象
alert( a === b ); // true
如果是两个不同的属性,他们就不相等,即使都是空的。
let a = {};
let b = {}; // 两个独立的对象alert( a == b ); // false
如果比较两个对象 obj1 > obj2
或者用一个对象比较原始值 obj == 5
,对象被转换成原始值。我们不久就会学习到对象的转化是如何实现的,但是事实上,上面的比较真的极少用到,要不就是你代码写错了。
常量对象
一个被 const
修饰的对象可以被修改。
例如:
const user = {name: "John"
};user.age = 25; // (*)alert(user.age); // 25
看起来好像 (*)
这行会报错,但是不是的,这完全没问题。这是因为 const
仅仅修饰 user
。在这里 user
始终存储的都是同一个对象的引用。引用的地址没有变,只是引用的对象被修改了。
如果你想把 user
赋值给其他的什么,那就会报错了,例如:
const user = {name: "John"
};// 错误(不能再给 User 赋值)
user = {name: "Pete"
};
…那么我们应该怎么样创建不可变的对象属性呢?如果想让 user.age = 25
这样的赋值报错呢。这也是可以的,
复制和合并,Object.assign
复制一个对象的变量也等同于创建了此对象的另一个引用。
那么我们该怎么复制一个对象呢?创建一份独立的拷贝,一份复制?
如果我们真的想这么做,就需要创建一个新的对象,遍历现有对象的属性,在原始值的状态下复制给新的对象。
像这样:
let user = {name: "John",age: 30
};let clone = {}; // 新的空对象// 复制所有的属性值
for (let key in user) {clone[key] = user[key];
}// 现在复制是独立的复制了
clone.name = "Pete"; // 改变它的值alert( user.name ); // 原对象属性值不变
我们也可以用[Object.assign](javascript:if(confirm(‘https://developer.mozilla.org/zh/docs/Web/JavaScript/Reference/Global_Objects/Object/assign \n\n���ļ����� Teleport Ultra ����, ��Ϊ ����һ�����·���ⲿ������Ϊ������ʼ��ַ�ĵ�ַ�� \n\n�����ڷ������ϴ���?’))window.location=‘https://developer.mozilla.org/zh/docs/Web/JavaScript/Reference/Global_Objects/Object/assign’) 来实现。
语法是:
Object.assign(dest[, src1, src2, src3...])
- 参数
dest
和src1, ..., srcN
(可以有很多个)是对象。 - 这个方法复制了
src1, ..., srcN
的所有对象到dest
。换句话说,从第二个参数开始,所有对象的属性都复制给了第一个参数对象,然后返回dest
。
例如,我们可以用这个方法来把几个对象合并成一个:
let user = { name: "John" };let permissions1 = { canView: true };
let permissions2 = { canEdit: true };// 把 permissions1 和 permissions2 的所有属性都拷贝给 user
Object.assign(user, permissions1, permissions2);// 现在 user = { name: "John", canView: true, canEdit: true }
如果接收的对象(user
)已经有了同样属性名的属性,前面的会被覆盖:
let user = { name: "John" };// 覆盖 name,增加 isAdmin
Object.assign(user, { name: "Pete", isAdmin: true });// 现在 user = { name: "Pete", isAdmin: true }
我们可以用 Object.assign
来代理简单的复制方法:
let user = {name: "John",age: 30
};let clone = Object.assign({}, user);
它复制了 user
对象所有的属性给了一个空对象,然后返回拷贝后的对象。事实上,这跟循环赋值一样,但是更短。
直到现在,我们是假设所有的 user
属性都是原始值,但是如果对象属性指向对象呢?
像这样:
let user = {name: "John",sizes: {height: 182,width: 50}
};alert( user.sizes.height ); // 182
现在,并不能拷贝 clone.sizes = user.sizes
,因为 user.sizes
是一个对象,它按引用拷贝。所以 clone
和 user
共享了一个对象。
像这样:
let user = {name: "John",sizes: {height: 182,width: 50}
};let clone = Object.assign({}, user);alert( user.sizes === clone.sizes ); // true,同一个对象// user 和 clone 共享 sizes 对象
user.sizes.width++; // 在这里改变一个属性的值
alert(clone.sizes.width); // 51,在这里查看属性的值
为了解决上面的的问题,我们在复制的时候应该检查 user[key]
的每一个值,如果是一个对象,我们再复制一遍这个对象,这叫做深拷贝。
有一个标准的深拷贝算法,解决上面和一些更复杂的情况,叫做 [Structured cloning algorithm](javascript:if(confirm(‘https://w3c.github.io/html/infrastructure.html \n\n���ļ����� Teleport Ultra ����, ��Ϊ ����һ�����·���ⲿ������Ϊ������ʼ��ַ�ĵ�ַ�� \n\n�����ڷ������ϴ���?’))window.location=‘https://w3c.github.io/html/infrastructure.html#internal-structured-cloning-algorithm’)。为了不重复造轮子,我们使用它的一个 JS 实现的库 [lodash](javascript:if(confirm(‘https://lodash.com/ \n\n���ļ����� Teleport Ultra ����, ��Ϊ ����һ�����·���ⲿ������Ϊ������ʼ��ַ�ĵ�ַ�� \n\n�����ڷ������ϴ���?’))window.location=‘https://lodash.com/’), 方法名叫做 [_.cloneDeep(obj)](javascript:if(confirm(‘https://lodash.com/docs \n\n���ļ����� Teleport Ultra ����, ��Ϊ ����һ�����·���ⲿ������Ϊ������ʼ��ַ�ĵ�ַ�� \n\n�����ڷ������ϴ���?’))window.location=‘https://lodash.com/docs#cloneDeep’)。
总结
对象是具有一些特殊特性的关联数组。
他们存储键值对:
- 属性的键必须是字符串或者符号(通常是字符串)。
- 值可以是任何类型。
我们可以用下面的方法获取属性:
- 点符号:
obj.property
。 - 方括号
obj["property"]
,方括号中可以使用变量obj[varWithKey]
。
其他操作:
- 删除属性:
delete obj.prop
。 - 检查属性是否存在:
"key" in obj
。 - 遍历对象:
for(let key in obj)
循环。
对象根据引用来赋值或者复制。换句话说,变量存的不是对象的"值",而是值的 “引用”(内存地址)。 所以复制变量或者传递变量到方法中只是复制了对象的引用。 所有的引用操作(像增加,删除属性)都作用于同一个对象。
深拷贝的话我们可以使用 Object.assign
或者 [_.cloneDeep(obj)](javascript:if(confirm(‘https://lodash.com/docs \n\n���ļ����� Teleport Ultra ����, ��Ϊ ����һ�����·���ⲿ������Ϊ������ʼ��ַ�ĵ�ַ�� \n\n�����ڷ������ϴ���?’))window.location=‘https://lodash.com/docs#cloneDeep’)。
我们在这一章学习的叫做“基本对象” — 对象。
JavaScript 中还有很多其他类型的对象:
Array
存储有序数据集合。Date
存储时间日期。Error
存储错误信息
bj[“property”],方括号中可以使用变量
obj[varWithKey]`。
其他操作:
- 删除属性:
delete obj.prop
。 - 检查属性是否存在:
"key" in obj
。 - 遍历对象:
for(let key in obj)
循环。
对象根据引用来赋值或者复制。换句话说,变量存的不是对象的"值",而是值的 “引用”(内存地址)。 所以复制变量或者传递变量到方法中只是复制了对象的引用。 所有的引用操作(像增加,删除属性)都作用于同一个对象。
深拷贝的话我们可以使用 Object.assign
或者 [_.cloneDeep(obj)](javascript:if(confirm(‘https://lodash.com/docs \n\n���ļ����� Teleport Ultra ����, ��Ϊ ����һ�����·���ⲿ������Ϊ������ʼ��ַ�ĵ�ַ�� \n\n�����ڷ������ϴ���?’))window.location=‘https://lodash.com/docs#cloneDeep’)。
我们在这一章学习的叫做“基本对象” — 对象。
JavaScript 中还有很多其他类型的对象:
Array
存储有序数据集合。Date
存储时间日期。Error
存储错误信息- …等
JavaScript对象 、堆与栈相关推荐
- js学习笔记(对象~堆和栈)
对象 为什么要用对象 让参数更少,代码更简洁 什么是对象 1.现实生活中:万物皆对象,对象是一个具体的事物,一个具体的事物就会有行为和特征.比如:一个人.一部手机 2.类和对象 (1)类:描述一类事物 ...
- JavaScript中堆与栈的区别
在理解堆与栈这两个概念时,需要放到具体的场景下去理解.一般情况下有两层含义: (1)内存操作场景下,堆与栈表示两种内存的管理方式. (2)数据结构场景下,堆与栈表示两种常用的数据结构. 1.内存操作场 ...
- JavaScript 中堆和栈的区别
JS变量都存放在内存中,而内存给变量开辟了两块区域,分别为栈区域和堆区域 栈像个容器,容量小速度快 堆像个房间,容量较大 讲这些之前我们先说说基本数据类型和引用数据类型 我们知道在js中的数据类型可以 ...
- JavaScript理解堆和栈
博主在思否上面找到一篇讲的不错的文章, 记录一下 点这里跳转
- 吃人的那些 Java 名词:对象、引用、堆、栈
作为一个有着 8 年 Java 编程经验的 IT 老兵,说起来很惭愧,我被 Java 当中的四五个名词一直困扰着:对象.引用.堆.栈.堆栈(栈可同堆栈,因此是四个名词,也是五个名词).每次我看到这几个 ...
- [转载]如何限制一个类对象只在栈(堆)上分配空间?
一般情况下,编写一个类,是可以在栈或者堆分配空间.但有些时候,你想编写一个只能在栈或者只能在堆上面分配空间的类.这能不能实现呢?仔细想想,其实也是可以滴. 在C++中,类的对象建立分为两种,一种是静态 ...
- 限制对象在堆或栈中声明
***********************************************声明*************************************************** ...
- [Java_kaikeba]java中堆和栈的区别(对象变量的理解)
.堆和栈都是java用来在RAM中存放数据的地方.与C++不同,java自动管理堆栈, 程序员不能直接设置堆栈. .区别 1. .栈中存放基本数据类型变量(int. ...
- 8.JVM 关于对象分配在堆、栈、TLAB的理解
引言 我们知道,一般在java程序中,new的对象是分配在堆空间中的,但是实际的情况是,大部分的new对象会进入堆空间中,而并非是全部的对象,还有另外两个地方可以存储new的对象,我们称之为栈上分配以 ...
- 详细讲解JavaScript中的堆和栈
1.栈(stack)和堆(heap) stack为自动分配的内存空间,它由系统自动释放: heap则是动态分配的内存,大小不定也不会自动释放. 2.基本类型和引用类型 基本类型:存放在栈stack内存 ...
最新文章
- C++中的volatile关键字
- [转载]hadoop集群默认配置和常用配置
- 只能匹配第一列吗_VLOOKUP会用了吗?不会的抓紧看
- 04springMVC结构,mvc模式,spring-mvc流程,spring-mvc的第一个例子,三种handlerMapping,几种控制器,springmvc基于注解的开发,文件上传,拦截器,s
- 【SpringBoot】SpringBoot之Bean之自动加载
- Java基础学习总结(90)——Java单元测试技巧
- 多租户系统技术优越性及架构选型---springCloud工作笔记167
- sam卡和sim卡区别_SAM卡槽是不是和SIM卡槽是一个东西,有高人知道么?
- ubuntu 12.04 配置内核崩溃自动重启及转存
- 谷歌seo外链Backlinks研究工具推荐
- 低功耗视频解码芯片-TVP5150
- Oracle analytics server(OAS) 支持 mysql 社区版配置
- 无人驾驶真体验!老百姓都能打得到的“共享无人车”来了
- css3 移动端video视频全屏,横屏展示,适配微信/打包成app
- 三星Android手机进入工程模式
- 手撕自动驾驶算法——IMU测量模型、运动模型、误差模型
- PB实现BASE64加解密
- 华为内部批判:“过度高薪”养了一群闲人
- vlookup中lookup_value是公式可以吗?
- shell运行html文件路径,PowerShell文件系统(二)访问文件和目录