变量、作用域与内存
数据类型
基本数据类型(原始值)
1》undefined
2》null
3》boolean
true、false
4》string
字符串不可变:想要修改某个变量中字符串的值,就必须销毁原来字符串,然后将新值保存到该变量
5》number
6》symbol
复杂数据类型
Object----对象
我们的变量可能存储的时基本数据类型,基本类型存储在栈中,此时的变量类型就是原始值,
也有可能存的是复杂数据类型,复杂数据类型存放在堆中,此时变量名存的是对应复杂数据的引用地址
深克隆与浅克隆:
将一个变量赋值给另一个变量,如果是简单数据类型,由于它存的是数据的原始值,会将原始值复制一份,然后存在另一个变量中;复制后两者相互独立互补影响
浅克隆:一个修改,另一个也受影响
let num1 = 10;
let num2 = num1;
console.log(num2); // 10
num2 = 20;
console.log(num1, num2); // 10,20
但是对于复杂数据类型,由于变量存的是复杂数据的引用地址,将一个变量复制给另一个变量时,复制的时应用地址,所以修改一个变量的内容,另一个也跟着改变
深克隆:克隆后相互独立,互不影响
let obj1 = { name: "马什么梅" };
let obj2 = obj1;
console.log(obj2); // { name: "马什么梅" }
obj2.name = "马冬梅";
console.log(obj1.name, obj2.name); // 马冬梅 马冬梅
实现深克隆最实用的方法就是 :
JSON.parse(JSON.stringify(obj))
typeof与instanceof类型判断
typeof
typeof检测原始类型(基本数据类型)的具体类型
返回值:数据的类型; 如果是null,返回的是object
console.log(typeof 666); // number
console.log(typeof "nb"); // string
console.log(typeof true); // boolean
console.log(typeof undefined); // undefined
console.log(typeof null); // object
console.log(typeof [1, 2]); // object
console.log(typeof { content: "hi" }); // object
const func = () => {
console.log("月亮不睡我不睡");
};
console.log(typeof func); // function
检测基本数据类型,返回的是具体数据类型,但是null返回的是object;检测引用类型的时候,返回的是object,但是函数返回的是object
补充:任何实现内部使用了call方法的对象,使用typeof都返回function,比如正则表达式;在Chrome7和Safari5及之前版本,上述浏览器的正则表达式内部实现使用了call,但是后面都是返回object了
instanceof
适用于检测引用类型的具体类型
返回值:布尔值
自己定义的构造函数,new 出来的实例,也可以检测
console.log([1, 2] instanceof Array); //true
console.log({ content: "hi" } instanceof Object); //true
const fn = () => {
console.log("我是秃头小宝贝");
};
console.log(fn instanceof Function); //true
console.log([1, 2] instanceof Object); //true
console.log({ content: "hi" } instanceof Array); //false
function Person(name) {
this.name = name;
}
const p = new Person("luka");
console.log(p instanceof Person); //true
// instanceof不能检测基本数据类型
console.log(66 instanceof Number); //false
console.log(null instanceof Object); //false
执行上下文与作用域链
上下文
上下文有全局上下文和函数上下文,函数上下文是在函数被调用的时候产生的;
var globalValue = "shake you body";
function greet() {
alert("hello");
}
greet();
首先全局上下文进栈,greet被调用时,产生一个函数上下文,进入栈中
上下文放在栈中,栈中的先进后出,就像往一个罐子中放东西,拿出来的时候,先拿出的是罐子顶部的东西,也就是后放进去的东西,那么首先进栈的全局上下文是在栈底,最先进入,但是最后出来;函数上下文在函数被调用时产生,放入栈中,执行完后退出来;而全局上下文在程序退出前出栈
上下文在其所有代码都执行完毕后被销毁
上下文的生命周期:
1》创建阶段
这个阶段会创建变量对象、确定this指向,变量作用域链等
2》执行阶段
完成变量的赋值,执行其他js代码等
3》销毁阶段
执行上下文出栈,对应的引用失去内存空间,等待回收
作用域链
上下文创建的时候会创建变量对象arguments,并确定变量对象的作用域链
每个执行上下文对应一个作用域;所以我们有全局作用域和函数作用域,对应全局上下文和函数的上下文
函数中首先会在自己的变量对象中查找,没找到会沿着作用域链往上查找,但是他不会往下一级查找
console.log(content); // hello
function greet() {
var content = "hi";
console.log(content); // hi
function response() {
var str = "你好";
console.log(content); //hi
}
response();
}
greet();
content greet(全局变量对象) ----- content response (greet函数变量对象))----- str(response函数变量对象)
response变量对象中没有找到,它就往greet变量对象中找,如果greet函数的变量对象中也没有,就从全局变量对象中找
es6增加了块级作用域的概念:由最近的一堆{}花括号界定;if块、while块、function块
变量
变量声明
var let const关键字都可以声明变量
比较三者:
1》变量提升不同;var 存在变量提升,let没有;或者说他们的变量提升不同
console.log(num); // undefined
var num = 1;
console.log(num); // ReferenceError: Cannot access 'num' before initialization
let num = 1;
let 在声明前使用变量会报错,也就是我们说的暂时性死区
2》作用域不同;var 声明的变量作用域在最近的上下文中(全局或函数的上下文),而let是块级作用域
var定义的age实在全局作用域中
if (true) {
var age = 20;
}
console.log(age); // 20
if形成了块级作用域,全局作用中域访问不到就报错了
if (true) {
let age = 20;
}
console.log(age); // ncaught ReferenceError: age is not defined
3》声明同名变量不同;var 可以在同一作用域下声明同名变量,而let不行,会报错
var str = "hihi";
var str = "嗨嗨";
console.log(str); //嗨嗨
let str = "hihi";
let str = "嗨嗨"; // Uncaught SyntaxError: Identifier 'str' has already been declared
console.log(str); // 嗨嗨
4》在全局上下文中声明变量,var声明的变量会添加到window对象中(如果没有使用var来声明,直接赋值也会添加到window对象上,不会报错),而let声明的变量不会被添加到window对象中
var num1 = 20;
let num2 = 22;
console.log(window.num1); // 20
console.log(window.num2); // undefined
const和let差不多,唯一不同的时它声明的是个常量,声明时必须赋值,声明之后,就不能重新赋值(引用类型只要不改变引用地址就行)
变量提升
一个上下文创建的时候会创建变量对象,创建变量对象的过程:
1》建立argument对象,他是一个伪数组,属性名是:0,1,……,属性值就是传入的值
2》函数提升;在变量对象中创建一个变量名为函数名,值为指向函数内存地址的引用
3》变量提升;在变量对象中以变量名建立一个属性,属性值为undefined;但是let/const声明的变量没有赋值undefined,所以不能提前使用
console.log(num);
var num = 0;
相当于:
var num
console.log(num)
num=0
函数的提升先于变量的提升,首先函数的声明提升,然后变量提升
如果 var 变量与函数同名,则在这个阶段,以函数值为准,在下一个阶段,函数值会被变量值覆盖
console.log(cal); // cal函数
var cal = 10;
function cal(num1, num2) {
return num1 - num2;
}
console.log(cal); // 10
相当于:
function cal() {
return num1 - num2;
};
var cal;
console.log(cal);
cal = 10;
console.log(cal);
内存
基本数据类型存在栈中,引用类型存在堆中
栈中存数据想罐子中放东西,先放的放在底下,取出的时候,最后才拿出来;栈中先进后出
堆数据结构是树状结构,从堆中拿数据就像从书架中找书
垃圾回收:基本思路就是确定变量不会再使用,就释放它的内存,这个过程是周期性的,隔一段时间回收一次
一般是函数的上下文执行完毕,函数上下文退出函数调用栈,解除变量引用,内存释放,等待垃圾回收;全局的上下文是再退出的时候才被销毁,所以我们尽量减少全局变量,也要尽量减少闭包
|