什么是对象?
一般资料上是说 : 对象是有多个属性构成.....
但是说实在话 , 真的感觉有点懵!!!
从头开始认识一下对象!!!
- 对象是一个复杂类型 , 由基本类型组成
var name = "张三"
var age = 18
var gender = "male"
// 这些基本类型组合在一起是有意义的 , 那么可不可以拿个东西把他们放在一起???
那就用一个"对象" , 放一下吧
var person = {
'name' : '张三',
'age' : 18,
gender: 'male',
}
- 对象的本质是一个hash表
hash表样例
| key | value | | ---- | ----- | | a | 1 | | b | 2 | | c | 3 |
哈希表: 存储键值对的表格
{ ... }
花括号是不是就像画了一张表!!!
里面的基本类型组成的 x:y,
结构不就是 key和value吗!!!
- 属性 <==>
x:y,
对应着这样的结构
一组键值对 , 就是一个属性
- 内存分配
前面说了 , 对象是基本类型的组合
基本类型通常都是以字节为单位 , 分配内存的 , Number:8个字节 String:2个字节单位
但是对象却可能包含着多个简单类型 , 所以需要分配的空间要 超过基本单位很多
基本类型的内存分配策略失效!!!
stack 内存分配失效 , heap内存闪亮登场
- 由于内存机制不同 , 所以不可以转换成基本类型的数据
一般来说可以转字符串 toString
- 对象的多层嵌套 , 是对树结构的最好模拟!!!
对象的内存结构
js内存分为代码区和数据区
数据区又分为stack内存 和 heap堆内存
stack内存:
线性表的结构 , 便于数据的查找 , 但是数据插入 , 删改 , 特别浪费性能
因为后面的元素要挨个后移
heap堆内存:
堆内存是链表结构的 , 便于数据的改动 , 查找速度可以优化「二分法」
小结:
因为对象由于其结构复杂性的要求 , 应该放在heap内存中
基本数据为了保证查询速度 , 数据放在stack内存
变量提升 , 在代码区命名
内存结构
- 代码区+stack+heap
- 一个数字占64位 , 一个字符占16位
- 值
- 简单 -->stack
- 复杂 -->heap 「stack存地址」
- js没有指针的明确概念
- stack地址引用
循环引用
var obj = {}
obj.self = obj
// 那么obj.self.self.self 是什么呢? 画内存图理解
面试题
var a = {n:1}
var b = a
a.x = a = {n:2}
alert(a.x) ? ==>undefined
alert(b.x) ? ==>[Object obejct]
分析一下为什么?
- 关键
a.x = a = {n:2}
- 浏览器是从左到右分析代码的 , 看到a.x时 , a是{n:1}的地址
- 然后把a的地址赋值给a.x属性
- 最后把{n:2} 的地址赋值给a
一画内存图 , 就清楚了!!!
垃圾回收
如果一个对象没有被引用 , 那他就是垃圾 , 内存就应该被回收
画内存图 , 具体分析
动手 , 不要动脑!!!
内存泄漏
ie6 的bug
关闭网页 , document 对象死了
但是上面监听的函数却依然在内存中 , 导致内存不够用 , 就是内存泄漏
深拷贝和浅拷贝
深拷贝 , 就是值复制一份 , 覆盖到stack
所以基本类型默认是深拷贝
var a='张三'
var b=a
console.log(b) //'张三'
浅拷贝 , 对象stack存的是地址 , 复制覆盖到stack的也是地址
会造成如下结果:
var obj = {name:'sun'}
var obj2 = obj
obj2.name = '涵涵'
console.log(obj1.name) //涵涵
修改obj2的属性 , obj也改变了
是因为obj和obj2引用了同一块内存
这就是浅拷贝
对象的深拷贝: 新开辟一个内存 , 复制obj的信息
js对象相关乱象
空值乱象
在内存分配一个空间 , 起好名字 ,但是暂时还没想好放什么数据!!!
怎么处理这种情况?
- js默认进行初始化为空
- js对于基本类型的空值为undefind
var a
console.log(a) // undefind
奇怪的地方来了 , js对于的对象类型的初始化 却是null
用null表示对象的值为空!
key引号乱象
定义时 , key可以不加引号
var obj = {
name:'张三',
age:18
}
读取值 , 却必须加引号
obj['name']
这一切都是js的错误
实际上定义的时候也是应该加上单引号的 , 但是js为了方便写代码的人 , 在编译器自动帮人加了 , 小白是不知道的
读数据的时候自然会犯错 , 太坑了
所以语法糖 , 并不一定是好事!!!
语法糖的缺陷
var obj = {
9a:"hah"
}// 报错 , 9a作为变量声明错误 , js编译器没有那么聪明
var obj = {
"9a":"hah"
}// 可以
var obj2 = {
a_b:'xix'
}// 报错 , 不支持 a_b
var obj2 = {
'a_b':'xix'
}
所以 : 语法糖的能力要弱一些 , key加引号 能力会强一些
但是 , 奈何不加引号简单啊 , 所以还是普及开来了!!! 人性如此!
类型错误
null 是object
typeof null //object
function 是function 而不是对像
var xxx = function() {
console.log('hah')
}
typeof xxx //function
instace of 和 value of
instance of 判断对象是否是父对象的实例
a instance of b // a是否是b的实例
value of 用来判断变量存储的基本数据 , 只适用于基本类型
对象的关键字
| 关键字 | 作用 | | ----------- | ------------ | | delete | 删除属性 | | in | 查找属性 | | typeof | 查看基本类型 | | value of | 值 | | instance of | 实例 |
属性的删除
delete 关键字
var obj = {
name:'张三'
}
delete obj['name']
属性的查找
in 关键字
'name' in obj
对象类型的判断
typeof 关键字
var obj = {}
var a = "hah"
typeof obj // object
typeof a // string
不是对象的对象
for in 遍历对象
var obj = {
name:'sun',
age:12
}
for(var key in obj) {
console.log(key) //打印key
console.log(obj.key) //打印值失败
// 因为obj.key <==>obj.'name' , 这样的语法是错误的
console.log(obj[key])
}
function是一个关键字
为什么要对函数对象搞特殊?
创建数组
var a = []
创建对象
var obj = {}
但是创建函数
function xxx() {
...
}
却需要用关键字去创建? 同为对象 , 为什么函数对象要搞特殊呢?
函数与对象的关系
函数 : 处理数据的集合
函数 : 存储数据的集合
数据通过对象来存储 , 通过函数来操作!!!
互为表里 , 不可分割的 好搭档!!!
函数的功能之一就是创建对象 , 返回对象的地址
变量分两类
- 存数数据的变量 用var声明
- 操作数据的变量 用function声明
这是代码区内存的规则
在stack和heap内存区 , 值的保存和对象的保存是一致的!!!
从代码重构角度理解
刚开始 , 想完成求和的数据操作
var a = 1
var b = 3
a+b //求和
每当我们想做求和操作的时候都要重复一次这样的操作 , 太麻烦了
var n1 = 10
var n2 = 15
n1+n2
var n3 = 11
var n4 = 17
n3+n4
....
//写了很多次太麻烦了
用函数变量进行代码优化
function sum(a,b) {
this.a = a
this.b = b
return a+b
}
sum(10,15)
sum(11,17)
...
//是不是简单很多 , 一次创造 , 多此复用
现在类似于sum()这样的功能函数越来越多 , 并且很多人大量的使用
那么干脆js自己在内存区事前存好这些函数 , 把这些函数放到一个对象中
这就是对象的原型 以及 原型链 构造函数的逐步发展的过程!!!
函数的内存结构
var f = {}
f.params = []
f.fbody = "xxx"
f.call = function() {
window.eval(f.fbody)
}
浏览器内置对象
内置对象 window
其实就是global 全局对象 , 但是无奈 , ESMC标准在后 , 浏览器实现在先
所以就沿用window对象了
window的属性
常用属性
| ECMAScript规定 | 浏览器私自实现 | | ----------------------- | --------------------- | | parseInt | alert 弹框提示 | | parseFloat | prompt 用户填写 | | Number() | confirm 确认 | | String() | console 开发者 | | Boolean() | document (文档) DOM | | Object() | history (浏览器) BOM | | eval() 执行传入的字符串 | |
Number()为例的基本属性
var n = 1
var n2 = new Number(1)
console.log(n2) //Number {1}
n 是基本类型的声明
n2 是基本类型的对象包装 , 包含了很多好用的方法 , 最关键的是「长得像java」
程序员都很懒的 , 自然只愿意用第一种 !!!
那么 , 第二种的好用的方法 , 用不到怎么办????
又是 , js语法糖
var n = 1
n.toString() // 不会报错 , 为什么???
js帮我们做了一层代理
当js引擎看到了 , 上面的代码时 ,
自动做了如下操作:
var temp = new Number(n)
return temp.toString()
n.toString()
实际上是执行的其他代码的返回结果
这个temp临时对象 , 返回值之后就从内存中清除了
var n = 1
n.xxx = "hah"
console.log(n.xxx) //undefinde
分析:
| 代码区 | stack | heap | | -------- | -------- | ---------------- | | n | 1 | | | temp | ADDR101 | {1} | | http://temp.xxx | ADDR 101 | {1, 'xxx’:’hah’} |
然后 , temp消失了
http://n.xxx 又会触发上述过程 , 可是这一次却没有赋值 , 所以js引擎默认赋值 , undefinde
小结:
- 构造函数
- 创建临时变量存储对象的地址
- 为对象绑定原型
- 返回对象的地址
- 基本类型的构造函数返回的是对象 , valueof 查值 , toString()转成对象
- 基本类型函数对象的原型是是Object()函数对象
- 也是我们通常意义上的函数原型链顶点
- 所有的对象都是通过构造函数链创建的
与其称之为原型链, 不如称之为构造函数树 , 更能直面问题核心
一个问题: obejct作为对象是谁创建的???
答;Function构造函数创建的
又问 : Function构造函数对象是谁创建的?
答 : 是Fucnction自己
所以一切的源头是一个Function()特例开始的 , 自己创建了自己!!!!!!!!!!!
标准库
ECMA标准规定的 , 必须实现的js对象
- Object对象
- Array对象
- Number()为例的基本类型的包装方法对象
- Math对象
- RegExp对象
- JSON对象
其实就是常用的构造函数而已
对象与New
New是一个语法糖
帮我们做三件事
var temp = {} // 创建空对象
temp.__proto__ = fun.prototype //绑定原型
return temp // 返回对象的地址
Number() 与new
var n = Number(1) // ==>Number()返回的是基本类型
var n2 = new Number(1) // 返回的是对象
Array()与new
var a = new Array(1,2,3)
var a = Array(1,2,3) //是一样的
function 也是
小结:
- 基本类型 用不用new 返回的类型是不一样的
- 复杂类型 , 用不用new 返回的类型是一样的