# 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]

上面代码执行过程:

  1. 变量x是一个数组是引用类型,传入fn(x)并执行
  2. x[0]=100,x指向的[12,23]的地址,这个堆地址里面的值被改变。
  3. x=[100],x的指向变了,此时指向[100]的地址。
  4. x[1] = 200,修改的是x指向[100]的地址里面的值。
  5. 函数执行结束,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.NaNNaN视为不相等.

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
Last Updated: 8/15/2020, 1:39:46 PM