ps:在社区找了个某大型互联网公司面试题,对比着知识点顺便把原来的服务器上的博客迁移过来 =.=!

类型判断

区分 js 中的undefined ,null ,“” , 0 和 false等….根据大佬推荐 其实应该去阅读loadsh的源码。小白表示暂时不看,以后再看。

凭自己理解分析一下:

1
2
3
4
5
6
7
8
9
<script>

alert(!0); //true
alert(!false); //true
alert(!undefined); //true
alert(!null); //true
alert(!''); //true

</script>

######首先,通过上面的代码可以知道 , 在if..else 循环中, 0、false、undefined、null、’’ 都是会进入else块的,也就是说:

1
2
3
4
5
6
7
8
9
10
Boolean(0)
false
Boolean(false)
false
Boolean(undefined)
false
Boolean(null)
false
Boolean('')
false

如上代码所示,它们的Boolean都是false.那它们的类型分别是什么呢?

1
2
3
4
5
6
typeof(undefined) == 'undefined'
typeof(null) == 'object'
typeof("") == 'string'
typeof(0) == 'number'
typeof(false) == 'boolean'

喔。它们的类型各不相同。那么问题来了 ,那他们进行 ‘= =’ 和 ‘===’ 呢。毕竟:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
undefined == null
true
undefined == ""
false
undefined == 0
false
undefined == false
false
null == ''
false
null ==0
false
null == false
false
'' == 0
true
'' == false
true
0 == false
true
感觉有点晕,这样下去 ’===‘ 岂不是更加复杂.那可能要了解一下 “= =”和 “===” 的区别了。
相等和全等
比较过程
  • 双等号:
  • (1)如果两个值类型相同,再进行三个等号(===)的比较。
  • (2)如果两个值类型不同,也有可能相等,需根据以下规则进行类型转换在比较:
  • 如果一个是null,一个是undefined,那么相等。 mmp 我说为什么 null == undefined 原来是规定 orz =.=!。
  • 如果一个是字符串,一个是数值,把字符串转换成数值之后再进行比较。
  • 三等号:
    • 如果类型不同,就一定不相等。
    • 如果两个都是数值,并且是同一个值,那么相等;如果其中至少一个是NaN,那么不相等。(判断一个值是否是NaN,只能使用isNaN( ) 来判断) isNAN
    • 如果两个都是字符串,每个位置的字符都一样,那么相等,否则不相等。
    • 如果两个值都是true,或是false,那么相等
    • 如果两个值都引用同一个对象或是函数,那么相等,否则不相等。 涉及到值和引用的问题
    • 如果两个值都是null,或是undefined,那么相等
      举两个栗子:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      undefined == null
      true //这个是规定
      '' == 0
      true //数字和字符串进行双等号比较时,虽然类型吧不同,但是会把字符串转为数字,空字串会转为0,非空字符串会转为数字,如果字符串内不是纯数字就会返回NaN。

      [1] == [1]
      false // 关于这个就比较复杂,看起来是双等号判断,但是因为他们的都是对象,所以进行了全等判断,全等判断中,JavaScript会比较其内部引用,当且仅当他们的引用指向内存中的相同对象(区域)时才相等,即他们在栈内存中的引用地址相同。其实[1] 和 [1] 根本就是两个不用的对象,他们在栈中分配了不同的地址。

      Number(null)
      0 //null 表示"没有对象",即该处不应该有值。
      Number(undefined)
      NaN //undefined 表示"缺少值",就是此处应该有一个值,但是还没有定义。
      附:
1
2
3
4
5
String(undefined) -> "undefined"
String(null) -> "null"
String("") -> ""
String(0) -> "0"
String(false) -> "false"

作用域

  • 你不懂JS:作用域与闭包 第一章:什么是作用域?

    • 作用域是一组规则,它决定了在哪里和如何查找一个变量(标识符)。这种查询也许是为了向这个变量赋值,这时变量是一个LHS引用,或者是为取得它的值,这时变量是一个RHS引用。
    • LHS引用得自赋值操作。作用域 相关的赋值可以通过 = 操作符发生,也可以通过向函数参数传递(赋予)参数值发生
    • JavaScript引擎在执行代码之前首先会编译它,这样做,它将 var a = 2; 这样的语句分割为两个分离的步骤:
      • 首先,var a 在当前 作用域 中声明。这是在最开始,代码执行之前实施的。
      • 稍后,a = 2 查找这个变量(LHS引用),并且如果找到就向它赋值。
    • 如果RHS查询在嵌套的作用域的任何地方都找不到一个值,这会导致引擎抛出一个 ReferenceError;如果LHS查询在嵌套的作用域的任何地方都找不到一个值,而且如果程序没有运行在 Strict 模式下,那么全局作用域中会创建一个变量并将它交给引擎。如果是在 Strict 模式下,则也会抛出 ReferenceEroor。
    • 如果RHS查询的值被找到了,但是你试着去做一些它做不到的事情,比如引用 null 或者 undefined 的属性,那么引擎会抛出错误 TypeError。
  • 你不懂JS:作用域与闭包 第二章:词法作用域

    • 词法作用域意味着作用域是由编写时函数被声明的位置的决策定义的。编译器的词法分析阶段实质上可以知道所有的标识符是在哪里和如何声明的,并如此在执行期间预测它们将如何被查询。
    • 在JavaScript中有两种机制可以“欺骗”词法作用域:eval(..) 和 with 。前者可以通过对一个拥有一个或多个声明的“代码”字符串进行求值,来(在运行时)修改现存的词法作用域。后者实质上是通过将一个对象引用看作一个“作用域”,并将这个对象的属性看作作用域中的标识符,(同样,也是在运行时)创建一个全新的词法作用域。
    • 这些机制的缺点是,它压制了引擎在作用域查询上进行编译期优化的能力,因为引擎不得不悲观地假定这样的优化是不合法的。这两种特性的结果就是代码将会运行的更慢。不要使用它们。
  • 你不懂JS:作用域与闭包 第三章:函数与块儿作用域

    • 在JavaScript中函数是最常见的作用域单位。在另一个函数内部声明的变量和函数,实质上对任何外围“作用域”都是“隐藏的”,这是优秀软件的一个有意的设计原则。
    • 但是函数绝不是唯一的作用域单位。块儿作用域指的是这样一种想法:变量和函数可以属于任意代码块儿(一般来说,就是任意的 { .. } ),而不是仅属于外围的函数。
    • 从ES3开始,try/catch 结构在 catch 子句上拥有块儿作用域。
    • 在ES6中,引入了 let 关键字( var 关键字的表兄弟)允许在任意代码块中声明变量。if (..) { let a = 2; }将会声明变量a,而它实质上劫持了 if 的 { .. } 块儿的作用域,并将自己附着在这里。
    • 虽然有些人对此深信不疑,但是块儿作用域不应当被认为是 var 函数作用域的一个彻头彻尾的替代品。两种机能是共存的,而且开发者们可以并且应当同时使用函数作用域和块儿作用域技术 —— 在它们各自可以产生更好,更易读/易维护代码的地方。

ps: mmp好晕

引用传递

js 中什么类型是引用传递, 什么类型是值传递? 如何将值类型的变量以引用的方式传递?
  • 基本类型:存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配。
    5种基本数据类型有Undefined、Null、Boolean、Number 和 String,它们是直接按值存放的,所以可以直接访问。
  • 引用类型:存放在堆内存中的对象,变量实际保存的是一个指针,这个指针指向另一个位置。每个空间大小不一样,要根据情况开进行特定的分配。
    当我们需要访问引用类型(如对象,数组,函数等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。

    另外设计到深度拷贝和浅拷贝,到时候重写一篇。

内存释放

JavaScript 中不同类型以及不同环境下变量的内存都是何时释放?

引用类型是在没有引用之后, 通过 v8 的 GC 自动回收, 值类型如果是处于闭包的情况下, 要等闭包没有引用才会被 GC 回收, 非闭包的情况下等待 v8 的新生代 (new space) 切换的时候回收.

与前端 Js 不同, 2年以上经验的 Node.js 一定要开始注意内存了, 不说对 v8 的 GC 有多了解, 基础的内存释放一定有概念了, 并且要开始注意内存泄漏的问题了.

你需要了解哪些操作一定会导致内存泄漏, 或者可以崩掉内存. 比如如下代码能否爆掉 V8 的内存?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let arr = [];
while(true)
arr.push(1);
然后上述代码与下方的情况有什么区别?

let arr = [];
while(true)
arr.push();
如果 push 的是 Buffer 情况又会有什么区别?

let arr = [];
while(true)
arr.push(new Buffer(1000));
思考完之后可以尝试找找别的情况如何爆掉 V8 的内存. 以及来聊聊内存泄漏?

function out() {
const bigData = new Buffer(100);
inner = function () {
void bigData;
}
}

对于内存释放问题,入行尚欠,学到再补充。

ES6 新特性

const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 修饰对象有什么意义?
  • const 定义的 Array 中间元素能被修改,const 定义的变量只是持有 Array 的地址,这个变量只是本身不能修改,而对于存在于堆内存中的 Array 本身是可以修改的。
  • 对于 const 声明,只是它的赋值操作被冻结了,而值不会因为 const 而不变。主要是预防在coding过程中的coder因为疏忽对变量的意外修改。