# JS核心机制
本文主要内容:数据类型、垃圾/内存机制、generator等。
# JS数据类型
# 1】代码执行分析
首先我们来看一道简单的面试题,相信大家都会做:
let a = 12
let b = a
b = 13
console.log(a)//12
let a = { n: 12 }
let b = a
b['n'] = 13
console.log(a.n)//13
let a = { n: 12 }
let b = a
b = { n: 13 }
console.log(a.n)//12
如果要把上面的执行原理讲清楚,还是需要知道一些JS数据类型,JS脚本运行机制的知识:
ECStack
执行环境栈:当我们浏览器加载页面的时候,它会形成一个执行环境,称为:ECStack
执行环境栈;目的是:让代码放进ECStack
中执行。它属于是栈内存(Stack)
EC执行上下文:在JS代码在浏览器跑起来(执行时)的时候,就会产生一个EC(GLOBAL)
全局执行上下文,此时会将这个全局执行上下文EC(GLOBAL)
,放到执行环境栈ECStack
中执行。
VO:Variable object
变量对象;作用:把执行上下文EC中创建的变量和变量的值,存储到[[Environment]]
中。
AO:activation object
活动对象;在函数上下文中,我们用活动对象(activation object, AO
)来表示变量对象;活动对象和变量对象其实是一个东西,区别在于活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化
我们再来一道面试题:
let x = [12,23]
function fn(y) {
y[0] = 100
y = [100]
y[1] = 200
console.log(y)//[100,200]
}
fn(x)
console.log(x)//[100,23]
上面代码执行过程:
- 变量x是一个数组是引用类型,传入
fn(x)
并执行 x[0]=100
,x指向的[12,23]
的地址,这个堆地址里面的值被改变。x=[100]
,x的指向变了,此时指向[100]
的地址。x[1] = 200
,修改的是x指向[100]
的地址里面的值。- 函数执行结束,x指向的
[100]
的地址,没有被函数外部所引用,所以别销毁回收;但是x指向的[12,23]
的地址还在全局变量中被引用,暂时不会被销毁。
# 2】typeof null为什么是'object'
不同的对象在底层原理的储存是用二进制表示的,在JavaScript中,如果二进制的前三位都为0的话,系统会自动判断它是Object类型,null的存储二进制刚好是000。所以null被判定为Object类型,但事实上null是一个基本类型。这是一个第一代js的bug。
- 000:对象类型
- 1:整型
- 100:字符串
- 110:布尔类型
# 3】数据类型转换(+运算符和操作符)
+
号在JS中不仅仅是数学运算,还可以字符串拼接,或者操作符
let res = 1
+ null //转成数字0
+ true //转成数字1
+ undefined //转成数字NaN
+ 'tencent' //数字一旦遇到字符串,就是字符串拼接。
+ false //直接转成字符串
+ [] //转成''空字符串
+ undefined //直接转出字符串
+ null //直接转成字符串
console.log(res)//NaNtencentfalseundefinednull
let a = '2'
console.log(+a, typeof +a)//2 number
//使用移位符,进行移位操作强转成数字类型
console.log(~a, typeof ~a, ~~a, typeof ~~a)//-3 number 2 number
console.log(a - 0, typeof (a - 0))//2 number
console.log(a + 0, typeof (a + 0))//20 string//不能强转成数字,而是字符串拼接
let b = 'true'
console.log(+b, typeof +b)//NaN number
# 4】JS的==、===和Object.is()比较
console.log([])//[]
console.log(![])//false
console.log([] == false)//true
console.log(![] == false)//true
console.log([] == ![])//true
# JS的相等比较:==
和===
==
在比较的时候可以转换左右两边的数据类型:
对象==对象
---比较地址对象==字符串
---对象转为字符串NaN==NaN
---NaN和自己以及其它任何值都不相等null==undefined
他俩在两个等号相等,三个等号不相等,并且和其它值都不相等- 剩下的情况,只要两边的类型不一样,最后都转换成数字,再比较。
console.log([].toString()==='')//true
console.log({}.toString())//[object Object]
//涉及检测数据类型Object.prototype.toString.call()
# Object.is()
==
运算符在判断相等前对两边的变量(如果它们不是同一类型) 进行强制转换 (这种行为的结果会将 "" == false
判断为 true
), 而 Object.is
不会强制转换两边的值。
与===
运算也不相同。 ===
运算符 (也包括 ==
运算符) 将数字 -0
和 +0
视为相等 ,而将Number.NaN
与NaN
视为不相等.
console.log(+0 == -0)//true
console.log(Object.is(+0, -0))//false
console.log(Number.NaN == NaN)//false
console.log(Object.is(Number.NaN, NaN))//true
console.log(NaN==NaN)//false
# 垃圾(内存)回收机制
浏览器有两种内存回收机制:
- 标记清楚法(Chrome)
//声明一个变量并赋值
var num = 10;//num指向栈内存中的10
//修改值
num = 20;//num指向栈内存中的20,之前的10就没有被引用了,所以销毁
//打印num
console.log('num:',num)//20
- 引用计数法(IE)
内存中该被回收清楚的变量,没有被清楚掉==>内存泄漏
内存只有栈内存和堆内存:
- 堆内存释放:变量 = null(指向为null)
- 栈内存释放:函数执行完,形成的执行上下文中,没有变量被上下文以外的内容占用,此上下文就会从执行环境栈中移除(释放),如果有被占用,则该上下文就被继续保留,压缩到执行环境栈底部。(闭包可能会导致内存泄漏,需要手动回收不用的变量)
//栈内存例子:
function A(){
let a = 0//变量a被下面的匿名函数所引用,
//而且该匿名函数被全局变量x所引用,需要保留A的执行上下文中的a变量
return function (){
a++
}
}
let x=A()
x()
x()
//此时变量a就被永久放在栈内存中,
例子二:使用执行函数闭包解决定时器问题
for(var i=0;i<10;i++){
setTimeout(function(){
console.log(i)
},100)
}//1010..
for(var i=0;i<10;i++){
(function(j){
setTimeout(function(){
console.log(j)
},100)
})(i)
}//0123456789
# 数组中常用的高阶方法
- forEach
- map
- filter
- find
- every
- some
- reduce
- ...
手写Jquery中的each方法:用来遍历数组,类数组对象,对象的方法
Object.prototype.each = function (callback) {
//this:调用方法的实例(数组/类数组对象/对象)
let length = this.length;
//判断数组、类数组对象
if (typeof length === 'number' && (length - 1) in this) {
//语法:key in Object判断对象是否存在该键
for (let i = 0; i < length; i++) {
let item = this[i];
let result = callback(item, i);
if (result === false) {
break;
}
this[i] = result;
}
return;
}
//对象
for (let key in this) {
if (!this.hasOwnProperty(key)) break;
let value = this[key];
let result = callback(value, key);
if (result === false) {
break;
}
this[key] = result;
}
}
function UseEach(param) {
param.each(function (item, index) {
//return false:等价于结束循环
//return 其他值:会把数组中当前项替换
//不return就不会替换
console.log(index, item)
})
}
let obj = { name: 'hh', age: 1 }
let arr = [1, 2]
UseEach(obj)
//name hh
//age 1
UseEach(arr)
//0 1
//1 2
使用数组reduce方法实现去重:
let arr = [1, 1, 2, 3, 4, 3]
let newarr = arr.reduce((first, second) => {
if (!first.includes(second)) {
first.push(second)
}
return first
}, [])
console.log(newarr)
手写实现数组的reduce方法:
Array.prototype.my_reduce = function (callback, initialValue) {
if (!Array.isArray(this) || !this.length || typeof callback !== 'function') {
return []
} else {
// 判断是否有初始值
let hasInitialValue = initialValue !== undefined;
let value = hasInitialValue ? initialValue : this[0];
for (let index = hasInitialValue ? 0 : 1; index < this.length; index++) {
const element = this[index];
value = callback(value, element, index, this)
}
return value
}
}
let arr = [1, 2, 3, 4, 5]
let res = arr.my_reduce((pre, cur, i, arr) => {
console.log(pre, cur, i, arr)
return pre + cur
}, 10)
console.log(res)//25