深入理解Javascript(一)内存、原型、闭包
1. 数据、变量和内存
1.1 数据类型判断
- typeof:
- 可以区别: 数值, 字符串, 布尔值, undefined, function,symbol
- 不能区别: null与对象, 数组与一般对象
- 返回的是数据类型的字符串表达形式
1
2
3
4
5a = 3;
console.log(typeof a = = = 'number');//注意引号
a = null;
console.log(typeof a) // 'object' - instanceof
- 专门用来判断对象数据的类型: Object, Array与Function
1
2
3
4
5
6
7
8
9var b1 = {
b2: [2, 'abc', console.log],
b3: function () {
console.log('b3()')
}
};
console.log(b1 instanceof Object, typeof b1); // true 'object'
console.log(b1.b2 instanceof Array, typeof b1.b2); // true 'object' //typeof不能区分数组与一般对象
console.log(b1.b3 instanceof Function, typeof b1.b3); // true 'function' - ===
- 可以判断: undefined和null
1
2a = null;
console.log(a===null); // true
1.1.1常见问题
- undefined与null的区别 undefined:声明,未赋值 null:声明,赋值为null。用于:①初始赋值,表明Object类型;②结束时,断开堆内存链接,被垃圾回收。
- 严格区分变量类型与数据类型
变量类型:
基本类型:保存数据
引用类型:保存地址值
数据类型:
基本类型
对象类型
1.2数据、变量和内存
- var a = xxx;a内存中保存的到底是什么? ①xxx是基本数据,保存的就是这个数据 ②xxx是对象,保存的是地址值 ③xxx是变量,依变量类型而定
- 关于引用变量的赋值问题 ①多个引用变量指向一个对象 ,一个变量的赋值发生变化,其他也随之变化 ②两个引用变量指向一个对象,修改一个变量指向另一个对象,另一变量依然指向前一个对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19a = { name: "Bob", age: 18 };
function fun(obj) {
obj = {age:15};
}
fun(a);
console.log(a.age);
//输出结果:18
//解析:传参的过程其实是赋值的过程。将a的地址值赋给obj,obj = {age:15}改变了obj的地址,并未改变a,故a.age仍为18
a = { name: "Bob", age: 18 };
function fun(obj) {
obj.age = 15;
}
fun(a);
console.log(a.age);
//输出结果:15
//解析:obj.age = 15改变了堆内存中age的值,a也随之改变 - JS如何管理内存?
内存生命周期:
分配内存空间,得到它的使用权;
存储数据,可以反复操作;
释放内存
释放内存:
为执行函数分配的栈内存: 函数执行完自动释放
存储对象的堆内存: 当内存没有引用指向时, 对象成为垃圾对象, 垃圾回收器后面就会回收释放此内存
- 浅拷贝与深拷贝 浅拷贝只拷贝一层,深层次的对象只拷贝引用(地址值);
深拷贝每一级别的数据都会拷贝
2. 函数高级
2.1对象
- 对象是多个数据的集合体(封装体),用于保存多个数据的容器,便于对多个数据进行统一管理。
- 对象的组成 属性:由属性名和属性值组成,属性名都是字符串类型, 属性值是任意类型 方法:是特别的属性(属性值是函数)
- 访问对象 .属性名 有时不能用 [‘属性名’]
- 只能用[‘属性名’]的情况 ①属性名包含非法字符 ②属性名不确定
2.2 函数
- 函数定义
1
2
3
4
5
6
7
8
9//函数声明
function fun1(){
}
//表达式,声明提前
var fun2 = function(){
}; - 函数的调用 ①test(); ②obj.test(); ③new Test(); ④test.call(obj); test.apply(obj); 让test临时成为obj的方法,从而改变this的指向
- 立即执行函数(IIFE) 作用:隐藏实现 ;不会污染命名空间;用于编码JS模块
2.3 this
任何函数本质上都是通过某个对象调用的,如未指定,这个对象默认是window
所有函数内部都有一个变量this,this指向调用函数的对象
确定this的指向:
- test(): window
- p.test(): p
- new test(): 新创建的对象
- p.call(obj,…): obj
2.4 原型prototype
2.4.1原型与原型对象
每个函数都有一个prototype属性,他指向一个object对象(原型对象)。
原型对象中有一个属性constructor,它指向函数本身。由此函数与原型对象构成了双向引用的关系。
我们可以通过Fun.prototype.test = xxx;来为构造函数的实例添加属性或方法。
1 | //输出Date的原型对象: |
1 | //通过.prototype.constructor获取当前函数 |
2.4.2 显式原型与隐式原型
每个函数Function都有一个prototype,即显式原型;
每个实例对象都有一个_ proto _,可称为隐式原型;
他们保存一样的地址值,共同指向原型对象。
1 | //创建构造函数 |
2.4.3 原型链
访问一个对象的属性时,
- 先在自身属性中查找,找到返回
- 如果没有, 再沿着__proto__这条链向上查找, 找到返回
- 如果最终没找到, 返回undefined
1 | /* |
设置对象的属性值时,不会查找原型链,如果当前对象没有此属性,则为其添加此属性并设置其值。
方法一般定义在原型中,属性一般通过构造函数定义在对象本身上。
1 | function Fun() { |
2.4.4 探索instanceof
a instanceof B
如果B的原型对象在a的隐式原型链上,则返回true
2.5 执行上下文与执行上下文栈
全局执行上下文:
- 在执行全局代码前,将window确定为全局执行上下文
- var定义的全局变量添加到window,值为undefined
- function声明的函数,添加到window的方法
- 为this赋值——window
- 执行全局代码
注:先变量提升,再函数提升
函数执行上下文:
- 在调用函数、执行函数之前,创建函数执行上下文对象(虚拟的对象,存在于栈中)
- 对局部数据进行预处理: 形参赋值为实参,添加到执行上下文的属性 argument(实参列表)添加到执行上下文的属性 var定义的局部变量添加为执行上下文的属性,值为undefined function声明的函数,添加到执行上下文的方法 为this赋值——谁调用的函数,谁就是this
- 开始执行函数代码
2.6 作用域与作用域链
作用域与执行上下文的区别与联系
- 区别1
- 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
- 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建;函数执行上下文是在调用函数时, 函数体代码执行之前创建
- 区别2
- 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
- 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
- 联系
- 执行上下文(对象)是从属于所在的作用域
- 全局上下文环境==>全局作用域
- 函数上下文环境==>对应的函数使用域
练习:
1 | var x = 10; |
1 | var fn = function () { |
2.7 闭包
2.7.1闭包简介
高阶函数:参数是函数或返回值是函数
- 闭包的产生 当子函数引用了父函数的变量(变量也可以是函数),就产生了闭包。 另:一个作用域有权访问另一个函数的局部变量,这个作用域就称为闭包。
- 常见的闭包: ①将函数作为返回值返回 ②将函数作为实参传递给另一个函数调用
1
2
3
4
5
6
7
8
9
10
11function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2 //也可以写成:returnfunction fn2() { a++; console.log(a); }
}
var f = fn1()
f() // 3
f() // 41
2
3
4
5
6function showDelay(msg,time){
setTimeout(function(){
alert(msg)
},time)
}
showDelay("lizhenduo",2000) - 闭包的作用 延长局部变量生命周期:函数内部变量在函数执行完后,仍然在内存中; 让函数外部可以操作到函数内部数据
- 闭包的生命周期 子函数定义执行完产生,成为垃圾对象后死亡
2.7.2 闭包应用:自定义JS模块
编写js文件,将所有数据和功能封装在一个函数内部(私有的),只向外暴露一个包含n个方法的对象,以供模块的使用者实现对应功能。
1 | (function () { |
2.7.3内存溢出与泄露
函数执行完之后,函数内的局部变量没有释放,仍然占用内存,容易造成内存泄露。
解决办法:能不用闭包就不用;及时释放
内存溢出:
一种程序运行时出现的错误。当程序运行需要的内存超过了剩余内存时,就抛出内存溢出的错误。
内存泄漏:
占用的内存未及时释放,内存泄漏积累多了就会导致内存溢出。
常见的内存泄漏:①意外的全局变量;②没有及时清理计时器或回调函数;③闭包
2.7.4 练习
见课件
2.8 递归
如果一个函数在内部可以调用其本身,就称为递归。为防止内存溢出,必须设置终止条件(return)。
1 | function fun(){ |