让人疑惑的代码

  1. 先看下面这两段代码:
    JavaScript
    JavaScript
    function foo(){
        var a = 1
        var b = a
        a = 2
        console.log(a)
        console.log(b)
    }
    foo()
    
    function foo(){
        var a = { name: "张三" }
        var b = a
        a.name = "李四" 
        console.log(a)
        console.log(b)
    }
    foo()
    
  2. 输出的结果是什么?
    1. 执行第一段代码,打印出来 a 的值是 2b 的值是 1
    2. 执行第二段代码,仅仅改变了 aname 的属性值,但是最终 ab 打印出来的值都是 { name: “李四” }

JavaScript 是什么类型的语言

  1. 每种编程语言都具有内建的数据类型,但它们的数据类型常有不同之处,使用方式也很不一样,比如 C 语言在定义变量之前,就需要确定变量的类型,可以看下面这段 C 代码:

    int main()
    {
      int a = 1;
      char* b = " 极客时间 ";
      bool c = true;
      c = a;
      return 0;
    }
    
  2. 上述代码声明变量的特点是:在声明变量之前需要先定义变量类型;把这种在使用之前就需要确认其变量数据类型的称为静态语言

  3. 相反地,把在运行过程中需要检查数据类型的语言称为动态语言;比如 JavaScript 就是动态语言,因为在声明变量之前并不需要确认其数据类型;

  4. 前面代码中,把 int 型的变量 a 赋值给了 bool 型的变量 c,这段代码也是可以编译执行的,因为在赋值过程中,C 编译器会把 int 型的变量悄悄转换为 bool 型的变量,通常把这种偷偷转换的操作称为隐式类型转换;而支持隐式类型转换的语言称为弱类型语言,不支持隐式类型转换的语言称为强类型语言;在这点上,CJavaScript 都是弱类型语言;

  5. 对于各种语言的类型,可以参考下图:

JavaScript 数据类型

  1. JavaScript 是一种弱类型的、动态的语言;

    1. 弱类型,意味着不需要告诉 JavaScript 引擎这个或那个变量是什么数据类型,JavaScript 引擎在运行代码的时候自己会计算出来;
    2. 动态,意味着可以使用同一个变量保存不同类型的数据;
  2. JavaScript 中的数据类型一种有 8 种:

    类型 数据类型 描述
    Boolean 原始类型 只有 true/false 两个值
    Null 原始类型 只有一个值 nulltypeof 检测 Null 类型时,返回的是 Object (是个 bug,一直没修改是为了兼容老的代码)
    Undefined 原始类型 一个没有被赋值的变量会有个默认值 undefined,变量提升的时候也是 undefined
    Number 原始类型 · 安全整数范围:±(253 - 1)±9,007,199,254,740,991
    · 特殊值:Infinity, -Infinity, NaN (Not a Number)
    Bigint 原始类型 · 可以表示任意大的整数,通过在数字后加 n 或调用 BigInt() 构造函数创建
    · 不能与 Number 混合运算 (需要显式转换),不支持 Math 对象中的方法
    String 原始类型 用于表示文本数据
    Symbol 原始类型 符号类型是唯一的并且不可修改的,通常用来作为 Objectkey
    Object 引用数据类型 包含了 字面量对象、数组、正则表达式、日期对象、Math、实例对象、函数…

检测有效数字使用 isNaN

  1. NaN 不是一个有效数字,但属于 number 数字类型,NaN 和谁都不相等;其他数据类型转换成数字类型,不能转换就是 NaN

  2. 示例代码:

    let a = Number("111");
    let b = Number("qwe");
        
    console.log(isNaN(a)); // false
    console.log(isNaN(b)); // true
    console.log(isNaN("A")); // true
    

类型转化

  1. 由于自动转换具有不确定性,建议在预期为布尔值、数值、字符串的地方,全部使用 Boolean()Number()String() 函数进行显式转换;遇到以下三种情况时,JavaScript 会自动转换数据类型,即转换是自动完成的,用户不可见:

    1. 第一种情况,不同类型的数据互相运算;
    2. 第二种情况,对非布尔值类型的数据求布尔值;
      1. 转化为 false 的值:false、undefined、null、‘’、0、NaN
      2. 转化为 true 的值:除了转化为 false 的值;
    3. 第三种情况,对非数值类型的值使用一元运算符(即 +-);
  2. 对象的转化(求对象的原始值)会先执行 valueOf() ,若 valueOf() 返回的值还是对象则会执行 toString()

    // eg1:
    let obj1 = {
      valueOf() {
          return 100;
      },
      toString() {
          return 200;
      },
    };
    console.log(true + obj1); // 101
    
    // eg2:
    let obj2 = {
      valueOf() {
          return {};
      },
      toString() {
          return 200;
      },
    };
    console.log(true + obj2); // 201
    

堆栈内存

  1. 在 JavaScript 的执行过程中, 主要有三种类型内存空间,分别是代码空间、栈空间和堆空间;

  2. 其中的代码空间主要是存储可执行代码的 (后面介绍),这里主要来说说栈空间 (调用栈,是用来存储执行上下文) 和堆空间,先看下面这段代码:

    function foo(){
        var a = "hello world"
        var b = a
        var c = {name:"hello world"}
        var d = c
    }
    foo()
    
  3. 当执行一段代码时,需要先编译,并创建执行上下文,然后再按照顺序执行代码;当执行到第 3 行代码时,其调用栈的状态如下:

  4. 接下来继续执行第 4 行代码,由于 JavaScript 引擎判断右边的值是一个引用类型,这时候处理的情况就不一样了,JavaScript 引擎并不是直接将该对象存放到变量环境中,而是将它分配到堆空间里面,分配后该对象会有一个在 “堆” 中的地址,然后再将该数据的地址写进 c 的变量值,最终分配好内存的示意图如下所示:

    1. 从上图可以清晰地观察到,对象类型是存放在堆空间的,在栈空间中只是保留了对象的引用地址,当 JavaScript 需要访问该数据的时候,是通过栈中的引用地址来访问的;
    2. 为什么一定要分 “堆”“栈” 两个存储空间呢?所有数据直接存放在 “栈” 中不就可以了吗?
    3. 答案是不可以的:这是因为 JavaScript 引擎需要用栈来维护程序执行期间上下文的状态,如果栈空间大了话,所有的数据都存放在栈空间里面,那么会影响到上下文切换的效率,进而又影响到整个程序的执行效率;比如文中的 foo 函数执行结束了,JavaScript 引擎需要离开当前的执行上下文,只需要将指针下移到上个执行上下文的地址就可以了,foo 函数执行上下文栈区空间全部回收,具体过程可以参考下图:
    4. 所以通常情况下,栈空间都不会设置太大,主要用来存放一些原始类型的小数据;而引用类型的数据占用的空间都比较大,所以这一类数据会被存放到堆中,堆空间很大,能存放很多大的数据,不过缺点是分配内存和回收内存都会占用一定的时间;
  5. 最后一步将变量 c 赋值给变量 d 是怎么执行的:在 JavaScript 中,赋值操作和其他语言有很大的不同,原始类型的赋值会完整复制变量值,而引用类型的赋值是复制引用地址,所以 d = c 的操作就是把 c 的引用地址赋值给 d,参考下图:

    1. 从图中可以看到,变量 c 和变量 d 都指向了同一个堆中的对象;
    2. 所以这就很好地解释了文章开头的那个问题,通过 c 修改 name 的值,变量 d 的值也跟着改变,归根结底它们是同一个对象;

面试题

第一题:(阿里)

let a = { n: 10 };
let b = a;
b.m = b = { n: 20 }; // 多个 '=' 的操作,从右向左赋值

console.log(a);
console.log(b);

第二题:(360)

函数执行之前的准备工作

  1. 初始化实参集合
  2. 创建形参变量并赋值
  3. 代码执行
let x = [12, 23];

function fn(y) {
    y[0] = 100;
    y = [100];
    y[1] = 200;
    console.log(y);
}

fn(x);
console.log(x);

第三题:

var x = 10;

~ function (x) {
    console.log(x);
    x = x || 20 && 30 || 40;
    console.log(x);
}();

console.log(x);

第四题:

let x = [1, 2], y = [3, 4];

~ function (x) {
    x.push('A');
    x = x.slice(0);
    x.push('B');
    x = y;
    x.push('C');
    // [3, 4, "C"] [3, 4, "C"]
    console.log(x, y);
}(x);

// [1, 2, "A"] [3, 4, "C"]
console.log(x, y);

object 的 key 值

  1. 只能是基本类型值,最后会转成字符串;

  2. 会把 引用类型值 转换成 字符串 [object Object]

JavaScript
JavaScript
var a = {}, b = '123', c = 123;
a[b] = 'b';
a[c] = 'c';
// a['123'] 等价 a[123],则 a[b]、a[c] 等于 c
console.log(a[b]); // c
let a = { x: 100 };
let b = { y: 200 };
let obj = {};

obj[a] = 'aaa'; //=> { '[object Object]': 'aaa' }
obj[b] = 'bbb'; //=> { '[object Object]': 'bbb' }

console.log(obj); //=> { '[object Object]': 'bbb' }

object 的 key 值为 Symbol

var a = {}, b = Symbol('123'), c = Symbol('123')
a[b] = 'b';
a[c] = 'c';
// Symbol 是 ES6 中新增的数据类型,创建的值为唯一值,b 不等于 c
console.log(a[b]); // b

值和引用

JavaScript
JavaScript
JavaScript
// 第一题
var foo = {
  n: 0,
  k: {
    n: 0,
  },
};
var bar = foo.k;
bar.n++;
bar = {
  n: 10,
};
bar = foo;
bar.n++;
bar = foo.n;
bar++;

console.log(foo.n, foo.k.n);
// 第二题
var foo = {
  n: 1,
};

var arr = [foo];

function method1(arr) {
  var bar = arr[0];
  arr.push(bar);
  bar.n++;
  arr = [bar];
  arr.push(bar);
  arr[1].n++;
}
function method2(foo) {
  foo.n++;
}
function method3(n) {
  n++;
}
method1(arr);
method2(foo);
method3(foo.n);

console.log(foo.n, arr.length);
// 第三题
var foo = { bar: 1 };
var arr1 = [1, 2, foo];
var arr2 = arr1.slice(1);

arr2[0]++;
arr2[1].bar++;
foo.bar++;
arr1[2].bar++;

console.log(arr1[1] === arr2[0]);
console.log(arr1[2] === arr2[1]);
console.log(foo.bar);

0.1 + 0.2 是否等于 0.3

console.log(parseInt('1010', 2)); // 10
console.log((10).toString(2)); // 1010

/*
  10进制 => 2进制 技巧
   - 整数位:当前位的值 *2^(n-1)
   - 小数位:把当前的不停乘 2 取整
      0.1 转成 2机制
      0.1 * 2 = 0.2 无整数 0.0
      0.2 * 2 = 0.4 无整数 0.00
      0.4 * 2 = 0.8 无整数 0.000
      0.8 * 2 = 1.6 无整数 0.0001
      0.6 * 2 = 1.2 无整数 0.00011
      0.2 * 2 = 0.4 无整数 0.000110
      ...... 无限循环小数
*/
console.log((0.1).toString(2));
// 0.0001100110011001100110011001100110011001100110011001101......
console.log((0.2).toString(2));
// 0.001100110011001100110011001100110011001100110011001101......
console.log((0.3).toString(2));
// 0.0100110011001100110011001100110011001100110011001101 转成十进制为 0.30000000000000004

a 等于什么值会让「a == 1 && a == 2 && a == 3 」条件成立;

JavaScript
JavaScript
JavaScript
JavaScript
JavaScript
// 方式一:数值和对象比较,先执行 valueOf,如果还是对象则再执行 toString
var a = {
    i: 1,
    toString() {
        return a.i++;
    }
};
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
}
// 方式二:数值和对象比较,先执行 valueOf,如果还是对象则再执行 toString
var a = {
    i: 1,
    valueOf() {
        return a.i++;
    }
};
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
}
// 方式三:
var a = [1, 2, 3];
a.toString = a.shift;
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
}
// 方式四:
var a = [1, 2, 3];
a.valueOf = a.shift;
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
}
// 方式五:
var i = 0;
Object.defineProperty(window, 'a', {
    get() {
        // 获取 window.a 的时候触发
        return ++i;
    },
    set() {
        // 给 window.a 设置属性值的时候触发
    }
});
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
}

[] == ![] 的结果是 true 吗

console.log([] == ![]); // true
// 1. 单目运算符优先级更高,![] 是一个表达式,则 ![] => fasle
// 2. [] 要转成原始值,先执行 valueOf,若返回值为对象继续执行 toString
//      [].valueOf() = [],[] == false
//      [].toString() = '','' == false
// 3. '' == false 
//      Number('') => 0
//      false => 0
// 4. 0 == 0 => true
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 让人疑惑的代码
  2. 2. JavaScript 是什么类型的语言
  3. 3. JavaScript 数据类型
    1. 3.1. 检测有效数字使用 isNaN
    2. 3.2. 类型转化
  4. 4. 堆栈内存
  5. 5. 面试题
    1. 5.1. 第一题:(阿里)
    2. 5.2. 第二题:(360)
    3. 5.3. 第三题:
    4. 5.4. 第四题:
    5. 5.5. object 的 key 值
    6. 5.6. object 的 key 值为 Symbol
    7. 5.7. 值和引用
    8. 5.8. 0.1 + 0.2 是否等于 0.3
    9. 5.9. a 等于什么值会让「a == 1 && a == 2 && a == 3 」条件成立;
    10. 5.10. [] == ![] 的结果是 true 吗