5 - 参数默认值、递归、预编译、暗示全局变量

139 阅读5分钟

一、参数默认值

  1. 没有传入实参时,形参默认值为undefined
  2. 可以在函数定义时,设置默认值(ES6写法)
// es5
function test(a, b) {
  var a = arguments[0] || 1; // 使用arguments对象判断是否传入实参:
  
  //使用typeof操作符检测某个实参是否为undefined来判断是否传入实参;
  var a = typeof(arguments[0]) === 'undefined' ? 2 : arguments[0];
  
  console.log(a); // 1
  console.log(b); // undefined
}

// es6,在函数定义时,给形参设置默认值
function test(a = 1) {
  console.log(a); // 1
}

test();

二、递归

  • 递归总是走到出口的时候,再一步一步向上赋值后返回结果
  • 递归的两个因素:1.找到计算的规律;2.找到出口,让这个递归函数可以结束。
/**
 * ! :阶乘
 *  n! = n * (n-1)!
 */
function fact(n) {
  if (n === 1) { //让递归结束的条件-出口
    return 1;
  }
  return n * fact(n - 1);
}
fact(5); //5!

//5

三、预编译

  1. var变量声明提升(变量赋值不提升);
  2. 函数声明整体提升,函数表达式不会提升;

1. js运行时会进行三件事:

  1. 语法分析 :检查通篇的语法错误(语法分析会在代码执行前对代码进行通篇检查,以排除一些低级错误)
  2. 预编译 :发生在代码执行的前一刻
  3. 解释执行:解释一行,执行一行

2. 函数预编译的步骤:

GO(global object)全局对象,全局上下文:

  1. 找变量声明
  2. 找函数声明
  3. 执行

AO (activation object)活跃对象,函数上下文(执行期上下文):函数执行前一刻会生成AO, 函数执行完成后,自身的AO会销毁。

  1. 函数形参、变量声明
  2. 函数实参赋值给形参
  3. 函数声明
  4. 执行

3.案例分析:

1. 案例1:

test();   //  1
function test() {
  console.log(1);
}
console.log(a); // undefined
var a = 2;

//打印结果:1  undefined

/**
*  过程:
*  一、预编译阶段: 1. var变量声明提升-> var a ; --> 这时候a的值为undefined
*                  2. 函数声明提升-> function test() {..}
*  二、函数执行:test();
*  三、变量赋值:a = 2;
*/

2. 案例2:

console.log(a);
function a(a) {
  var a = 10;
  var a = function() {
  }
}
var a = 1;

//打印结果:function a(){...}
/**
*  过程:
*  一、预编译阶段: 1. var变量声明提升-> var a ; --> 这时候a的值为undefined
*                  2. function a() {...} -> 这时候a变为function a() {...}
*  二、函数执行: console.log(a); -> function a() {...}
*  三、变量赋值:a = 1;
*/

3. 案例3:

function test(a) {
  console.log(a); // function a() {}
  var a = 1;
  console.log(a); // 1
  function a() {}
  console.log(a); // 1
  var b = function() {}
  console.log(b); // function() {}
  function d() {}
}
test(2);

//打印结果:function a() {}    1    1    function() {}

/**
* 过程:
* test(2)执行,产生test(2)的AO(执行期上下文)
* AO = {
*       a:undefined -> a = 2 -> a:function a() {...} -> a = 1
*       b:undefined -> b = function b() {...}
*       d:undefined -> d:function b() {...}
*      }
**/

4. 案例4:

/**
 * 1. GO = {
 *          a : function a() {}
 *         }
 * 2. a函数执行前生成自身的AO
 *    AO = {
 *          b : function b() {}
 *         }
 * 3. b函数执行前生成自身的AO
 *    AO = {
 *          c : function c() {}
 *         }
 * 4. c函数执行前生成自身的AO
 *    AO = {}
 */
function a() {
  function b() {
    function c() {
    }
    c();
  }
  b();
}
a();

/** 
 * 过程:
 *
 * a定义:a.[[scope]] -> 0: GO
 * a执行:a.[[scope]] -> 0: a的AO
 *                       1:GO
 * b定义:b.[[scope]] -> 0:a的AO
 *                       1:GO
 * b执行:b.[[scope]] -> 0: b的AO
 *                       1:a的AO
 *                       2:GO
 * c定义:c.[[scope]] -> 0: b的AO
 *                       1:a的AO
 *                       2:GO
 * c执行:c.[[scope]] -> 0:c的AO
 *                       1:b的AO
 *                       2:a的AO
 *                       3:GO
 * c结束:c.[[scope]] -> 0:b的AO
 *                       1:a的AO
 *                       2:GO
 * b结束:b.[[scope]] -> 0:a的AO
 *                       1:GO
 *        c.[[scope]]销毁
 * a结束:a.[[scope]] -> 0: GO
 *        b.[[scope]] -> 销毁
 */

四、暗示全局变量

  1. 在全局环境下,用var声明的变量会成为window对象上的属性;
  2. 未声明直接赋值的变量,会存到window里面,成为window对象上的属性;
// 1. 全局var声明
var a = 1;
    b = 2;
console.log(window.a); // 1
console.log(window.b); // 2

// 2.函数内部的var声明
function test() {
  // 1. var a ; 2. b = 1 ; 3. a = b; 
  // 所以这里的b是全局的,它的作用域是window,在函数外部也可以访问,
  // a在函数内部声明了,所以a的作用域为test(),在函数外部不可访问
  var a = b = 1; 
}
test();

// 访问对象中不存在的属性,会返回undefined
console.log(window.a); // undefined
console.log(window.b); // 1

1. 案例1:

function test() {
  // 函数内部可用return语句终止函数执行,返回对应的值
  return a;
  a = 1;
  function a() {}
  var a = 2;
}
console.log(test()); // function a() {}

// 打印结果:function a() {}

/**
* 思路:
* test()执行,产生test()的AO(执行期上下文)
* 1. 函数声明提升: var a ; 2. 函数声明提升: function a() {...}
* AO = {
*       a:undefined -> a:function a() {...} -> 函数终止执行,返回function a() {}
*      }
**/

2. 案例2:

a = 1;
function test(e) {
  function e() {}
  arguments[0] = 2; //实参的第一位赋值为2
  console.log(e); // 2
  // 此时变量a的值为undefined,不执行if语句内容
  if (a) {  // 函数内部重新声明了var a; a为undefined,所以没进判断语句,b也为undefined
    var b = 3;
  }
  var c;
  a = 4;
  var a;
  console.log(b); // undefined 上面的判断没进去,所以b只声明没赋值,为undefined
  // 暗示全局变量
  f = 5;
  console.log(c); // undefined c只声明,没赋值,所以为undefined
  console.log(a); // 4  var a ; -> a = 4 ;
}
var a;
test(1);
console.log(a); // 1 //在函数内部,用了var声明a,所以函数内部的a,在函数外部访问不到,a = 1;
console.log(f); // 5  // 函数内部,f没用var声明,所以f是挂载在window下的,外面可访问

//打印结果:2  undefined  undefined  4  1  5

/**
* 思路:
*  GO = {
*         a:undefined -> 1 
*         test: function test(e){...}
*         f:undefined -> 5
*       }
*
* AO = {
*        e : undefined -> 1 -> function e() {} -> 2
*        b : undefined
*        c : undefined
*        a : undefined -> 4
*      }
*/

六、面试题

1. 题目1:

var a = false + 1;
console.log(a);

// 打印结果: 1 
// Number(false) = 0; 0 + 1 = 1;

2. 题目2:

var b = false == 1;
console.log(b); 

//打印结果: false
// var a ; -> false == 1 : false ; -> a = false

3. 题目3:

if (typeof(a) && (-true) + (+undefined) + '') {
  console.log('通过了'); // 执行
} else {
  console.log('没通过');
}

// 打印结果: '通过了'

/**
*  思路:
*  && 左边:typeof(a) = "number";
*  && 右边:
*  1. (-true) -> (-Number(true)) -> -1
*  2. (+undefined) -> (+Number(undefined)) -> NaN
*  3. -1 + NaN + '' -> 'NaN'
*  判断结果:Boolean('NaN') -> true
*/

4. 题目4:

console.log(!!' ' + !!'' - !!false || '通过了');

//打印结果:1

/**
*  思路:
*  null、undefined、0、''、 NaN、false都为假,除了以上6个,其他的都为真;
*  1. 遇到 &&、|| 、! 会转成Boolean类型
*  2. Boolean(' ') : true  -> !!true: true
*  3. Boolean('') : false  -> !!false: false
*  4. !!false -> false
*  5. true + false - false -> Number(true) = 1 , Number(false) = 0 -> 1 + 0 - 0 = 1
*  6. 1 || '通过了' -> 1 (||或运算符:遇到假就往后走;遇到真或者走到最后就返回当前值;)
*  
*/