提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
Chapter01---基础篇(接续)
提示
基础篇的前两节
三、引用值、原始值的复制
1.引用值
引用值就是保存在内存中的对象,使用instanceof
判断其类型,亦可用toString()
方法来进行粗略判断(函数和数组除外
);原始值和函数一般使用typeof
判断其类型。
代码如下(示例):
let set = new Set();
let map = new Map();
let obj = {};
let sum = (a, b) => a + b;
let arr = [1, 2, 3, 4];
console.log(set instanceof Set); // true
console.log(map instanceof Map); // true
console.log(obj instanceof Object); // true
console.log(sum instanceof Object); // true
console.log(arr instanceof Array); // true
console.log(set.toString()); // [object Set]
console.log(map.toString()); // [object Map]
console.log(obj.toString()); // [object Object]
console.log(sum.toString()); // (a, b) => a + b
console.log(arr.toString()); // 1,2,3,4
2.复制。
-
原始值复制
:
通过变量把一个原始值赋值给另一个变量时,原始值会被复制到新变量的位置。
代码如下(示例):let a = 23; let b = a; //将a复制给b console.log(a === b); //true b = 22; //修改b console.log(b === a); //false
-
引用值复制
:
通过变量把一个引用值赋值给另一个变量时,其实是复制的指向引用值的指针
,因此会出现一个变量对对象进行修改,会反映在另一个变量上,即多个变量对同一个对象进行操作
现象。对于引用值的复制往往有两种,即浅复制
和深复制
。
代码如下(示例):
浅复制
let arr1 = [1, 2, 3, 4]; let arr2 = arr1; //将arr1复制给arr2 console.log(arr1 === arr2); //true arr1[0] = 0; // 修改arr1的第一个元素 console.log(arr2[0]); // 0
深复制
let arr1 = [1, 2, 3, 4]; let arr2 = []; for (let i = 0; i < arr1.length; i++){ arr2[i] = arr1[i]; } console.log(arr1 === arr2); // false arr1[0] = 0; // 修改arr1的第一个元素 console.log(arr2[0]); // 1
-
原始值引用值深度复制
例子:function deepCopy(target) { //深度复制 if (target === null) { //复制null return target; } const primitiveTypes = [ "undefined", "number", "boolean", "symbol", "string", "function" ]; //原始类型和function const targetType = typeof target; for (const type of primitiveTypes) { //复制原始值和function if (targetType === type) { return target; } } delete primitiveTypes; //复制引用值 if (target instanceof Array) {//数组 const tem = []; for (let i = 0; i < target.length; i++) { tem[i] = deepCopy(target[i]); } return tem; } else if (target instanceof Set) {// 集合 const set = new Set(); for (const e of target) { set.add(deepCopy(e)); } return set; } else if (target instanceof WeakSet) {// WeakSet const set = new WeakSet(); for (const e of target) { set.add(deepCopy(e)); } return set; } else if (target instanceof Map) { const map = new Map(); for (const [key, e] of target.entries()) { map.set(key, deepCopy(e)); } return map; } else if (target instanceof WeakMap) { const map = new WeakMap(); for (const [key, e] of target.entries()) { map.set(key, deepCopy(e)); } return map; } else if (target instanceof Object) { const obj = {}; for (let key in target) { obj[key] = deepCopy(target[key]); } return obj; } } let obj1 = { map: new Map(), set: new Set([1, 2, 3, 4, 4]), arr: [1, 2, 3, { name: 12, print: function() { console.log(this.name); } }], flag: true, print() { console.log(this.arr); }, nullObj: null }; let obj2 = deepCopy(obj1);
四、执行上下文和作用域以及垃圾回收机制
1.执行上下文和作用域:
-
变量和函数的
上下文决定了它们可以访问哪些数据,以及它们的行为
。每个上下文都有一个关联的对象,同时这个上下文中定义的所有变量和函数都存在于这个对象上。 -
全局上下文就是最外层的上下文,在浏览器中通过
var定义的变量和函数都会成为window对象的属性和方法
。 -
上下文会在其所有代码都执行完毕后销毁,包括定义在该上下文上的变量和函数。
-
函数作用域
,当代码执行流进入函数时,函数的上下文会被推到一个上下文栈
中。在函数执行完毕后,该函数上下文会弹出上下文栈,并且将控制权交还给之前的执行上下文。 -
上下文中的代码在执行的时候,会创建变量对象的一个作用域链。这个
作用域链决定了各级上下文中的代码在访问变量和函数的顺序
。全局对象始终处于作用域链的末端,当前执行上下文处于作用域链的前端。访问顺序是从作用域链的前端逐级向末端访问。 -
函数参数也遵循上述原则。
-
作用域增强:某些语句会导致在作用域链的前端临时添加一个上下文,这个上下文会在代码执行后删除。以下两种情况会出现作用域链增强:
try/catch 的catch块
、with语句
代码如下(示例)://全局作用域 let a = 10; function fun1(){//函数作用域1 let b = 11; console.log(a);//10, 查找a的顺序为:函数作用域1---->全局作用域 function fun2(){//函数作用域2 let c = 12; console.log(a);//10, 查找a的顺序为:函数作用域2---->函数作用域1---->全局作用域 } fun2(); } fun1();
2.垃圾回收机制
-
垃圾回收机制
:
JavaScript是使用垃圾回收的语言,也就是执行负责在代码执行时管理内存。基本思路是,确定哪个变量不在使用,然后释放它所占用的内存。常见的策略有:标记清理
和计数引用
。 -
内存泄漏
:
JavaScript中的内存泄漏大部分是由于不合理的引用导致的。
1.意外声明全局变量导致内存泄漏,如:function func(){ //未使用关键字进行声明,默认使用var,即函数执行后name是一个全局变量 name = 23; }
2.定时器会悄悄的导致内存泄漏,如下列代码,只要定时器一直运行,name就会一直占用内存:
let name = “Jack”; setInterval(()=>{ console.log(name); }, 100);
3.JavaScript闭包会导致内存泄漏,如下列代码:
function outer(){ let name = "Jack"; return function inner(){ return name; } }