导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

冷门技能

写法、类型

函数声明

function 函数名(参数) {
  // 执行语句
}

⭐️ 函数表达式(有名)

var test = function test1() {
  console.log(test1.name); // test1 内部可见
}

console.log(test.name); // test1

test(); // 可执行
// test1(); // 报错,test1函数外部不可见

函数表达式(匿名)

// 整体是个匿名函数表达式,function匿名函数这个值,这个整体是字面量
var test = function() {
  var a = 1, b = 1; // 推荐
  // var a = b = 1; //不推荐,此时的 b 并没有被 var ,b 为全局变量
}

var a = 10; // 10 这个值就是字面量
var a = function b() {
  console.log(a, b); // [Function: b] [Function: b]
  console.log(a.name, b.name); // b b
}
a();

形参、实参

function test(a, b) { // a, b 是形参

}
test(1, 2); // 1, 2 是实参

⭐️ 形参、实参个数可以不相等

function test(a, b, c) {
  console.log(arguments.length); // 实参有 2 个:1 跟 2
  console.log(test.length); // 形参有3个:a, b, c
}
test(1, 2);
(function() {}).length;             // 0

实参求和

function sum() {
  return [...arguments].reduce((total, cur) => total += cur, 0);
}

sum(1, 2, 3);

⭐️ arguments

function test(a, b) {
  a = 3;
  console.log(arguments[0]); // 值为3,修改形参会影响 arguments 映射的值

  b = 2;
  console.log(arguments[1]);
  // 值为undefined,由于没有传入形参对应的实参,导致更改形参无法改变 arguments 中对应的值
}

test(1);

return

function test() {
  console.log('1'); // 1
  return 123; // test函数返回123
  console.log('2'); // 未被执行,不会打印
}

⭐️ 函数参数默认值

function test(a, b) {
	console.log(a, b); // 1, undefined
}
test(1);
function test(a = 1, b = 2) {
  console.log(a, b); // 1, 2
}
test();
function test(a = 1, b) {
  console.log(a, b); // 1, 2
}
test(undefined, 2);
function test(a, b) {
  var a = typeof(arguments[0]) !== 'undefined' ? arguments[0] : 1;
  var b = typeof(arguments[1]) !== 'undefined' ? arguments[1] : 2;
  console.log(a, b);
}
test();

⭐️ 预编译

⭐️ 运行过程

  1. 通篇检查是否有语法错误,有则直接抛错,不执行任何语句
  2. 预编译
  3. 解释一行,执行一行

变量提升

test(); // 1
function test() {
	console.log(1);
}

console.log(a); // undefined
var a;

隐式全局变量 Implicit global variable

function test() {
  var a = b = 1;
}

test();
console.log(b); // 1 (b挂在了window,因为没有var声明)
console.log(window.b); // 1
console.log(a); // ReferenceError: a is not defined
console.log(window.a); // undefined

⭐️ AO(activation object)活跃对象,函数上下文

  1. 寻找形参和变量声明没用 var 声明的变量不提升变量名,不声明 var 的变量被修改,当前作用域有此变量的,优先修改此作用域内的变量
  2. 实参值赋值给形参
  3. 寻找函数声明并赋值
  4. 执行
function test(a) {
  console.log(a);
  var a = 1;
  console.log(a);
  function a() {}
  console.log(a);
  var b = function() {}
  console.log(b);
  function d() {}
}
test(2);
// function a() {}
// 1
// 1
// function() {}
AO = {
  a: undefined ->
     2 ->,
     function a(){} ->
     1,

  b: undefined ->
     function() {},

  d: function d() {}
}
function test(a, b) {
  console.log(a);
  c = 0;
  var c;
  a = 5;
  b = 6;
  console.log(b);
  function b() {}
  function d() {}
  console.log(b);
}
test(1, 2);
 // 1
 // 6
 // 6
AO = {
  a: undefined ->
      1 ->
      5,

  b: undefined ->
      2 ->
      function b() {} ->
      6,
  c: undefined ->
      0,
  d: function d() {}
}

没用 var 声明的变量不会有变量提升

console.log(a);
a = 2;

// 答案:

// ReferenceError: a is not defined

// vs
console.log(a);
var a = 2;

// 答案:

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

// 答案:

// ReferenceError: a is not defined

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

// 答案:

// undefined

代码块、if 语句中,通过 var 声明的变量,会提升到外层作用域(也就是里外共享var声明的变量)

console.log(a);
if (true) {
  a = 2;
}

// 答案:

// ReferenceError: a is not defined

// vs

console.log(a);
if (true) {
  var a = 2;
}

// 答案:

// undefined

// ----------

var a = 1;

{
  a = 3;
  console.log(a);
  var a;
}

console.log(a);

// 答案:

// 3  3
function test() {
  console.log(a);
  if (true) {
    a = 2;
  }
}
test();

// 答案:

// ReferenceError: a is not defined

// vs
function test() {
  console.log(a);
  if (true) {
    var a = 2;
  }
}
test();

// 答案:

// undefined
{
  a = 3;
}

console.log(a);

// 答案:

// 3 (a 不会提升 但会执行,挂载在 window 下)

// vs
console.log(a);
{
  a = 3;
}

// 答案:

// ReferenceError: a is not defined

// vs
{
	var a = 3;
}

console.log(a);

// 答案:

// 3

// ----------------------

if (true) {
  a = 3;
}
console.log(a);

// 答案:

// 3

// vs
if (false) {
  a = 3;
}
console.log(a);

// 答案:

// Uncaught ReferenceError: a is not defined

// ----------------------

if (true) {
  var a = 3;
}
console.log(a);

// 答案:

// 3

// vs

if (false) {
	var a = 3;
}
console.log(a);

// 答案:

// undefined

GO(global object)全局上下文

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

GO === window

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

*//* 答案:

// 1

GO = {
	a: undefiend ->
  	 function a(){} -> 
     1
}
console.log(a, b);
function a() {}
var b = function() {};

// 答案:

// function a() {} undefined
GO = {
	b: undefined,
  a: function a() {}
}
function test() {
	var a = b = 1;
  console.log(a);
}

test();

// 答案:

// 1

GO = {
	b: 1
}

AO = {
	a: undefined ->
  	1
}
var b = 3;
console.log(a);
function a(a) {
	console.log(a);
  var a = 2;
  console.log(a);
  function a() {}
  var b = 5;
  console.log(b);
}

a(1);

// 答案:

 // function a(a) {}
 // function a() {}
 // 2
 // 5

GO = {
	b: 3
  a: function a() {}
}

AO = {
	a: undefined ->
  	 1 ->
  	 function a() {} ->
		 2,
  b: undefined ->
     5
}

AO = {
	b: undefined ->
  	 5
}
a = 1;
function test() {
	console.log(a);
  a = 2;
  console.log(a);
  var a = 3;
  console.log(a);
}
test();

// 答案:

 //undefined
 // 2
 // 3

GO = {
	a: undefined ->
  	 1,
  test: function test() {}
}
AO = {
	a: undefined ->
  	 2 ->
  	 3
}
function test() {
	console.log(b);
  if (a) {
  	var b = 2;
  }
  c = 3;
  console.log(c);
}
var a;
test();
a = 1;
console.log(a);

// 答案:

// undefined
// 3
// 1

GO = {
	test: function test() {},
  a: undefined ->
      1,
  c: 3
}

AO = {
	b: undefined
}

⭐️ if、代码块陷进

假定 if 中有一个函数声明 function a() {}

  1. if 未执行时

    1.1 if 里的函数声明首先把变量名 a 提升到当前作用域(不是块作用域)的最顶部 a = undefined

  2. if 执行时

    2.1 函数声明整体会被提升到块作用域顶部

    2.2 执行到函数声明语句时,会把此刻块作用域里的 a 赋值到当前作用域同名变量 a

  3. 特殊情况

    3.1 如果 if、代码块外边已经用 let 或者 const 声明了 a 变量,则 不执行 1.12.2 步骤

console.log(b);
if (true) {
  console.log(b);
  function b() {}
}

// 答案:

// undefined  function b

// 分析:

// 因为if中,function b() {} 会被看做函数表达式
// 所以会把函数名 b 变量提升到当前作用域的最顶部(也就是全局执行器上下文),b 定义没值 undefined

// if 在执行时,会首先把函数声明提升到 if 代码块内部的顶端
// 所以内部打印 function b
var f = function() {return true;};
var g = function() {return false;};
(function() {
	console.log(g);
  if (g() && [] == ![]) {
  	f = function f() {return false;};
    function g() {return true;};
  }
})();

// 答案:

// 见下图

Untitled

分析:

Untitled

Untitled

上面打断点会发现,function g的g变量是提升到if外边了,但此刻还是undefined。而iife外边的f,g此刻在iife中是可以访问的。但由于iife中有了同名g,所以还是undefined,然后if判断是g()由于g是undefined不是函数所以报错了。另外if中的f前面由于没有var ,所以改的是iife外边的f

练习

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

// 答案:

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

// 答案:

// 2
a = 1;
function test(e) {
	function e() {}
  arguments[0] = 2;
  console.log(e);
  if (a) {
  	var b = 3;
  }
  var c;
  a = 4;
  var a;
  console.log(b);
  f = 5;
  console.log(c);
  console.log(a);
}
var a;
test(1);

// 答案:

// 2 undefined undefined 4

// 分析:

go = {
	a: undefined -> 1,
  test: function test() {},
  f: undefined -> 5
}

ao = {
	a: undefined ->
     4,
  b: undefined,
  c: undefined,
  e: undefined ->
     1 ->
     function e() {} ->
     2
}

面试题

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

// 答案:

// 1 

// hint: 隐式转换,没有字符串,把非字符串转基本类型,再to number

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

// 答案:

// false

if (typeof(a) && (-true) + (+ undefined) + '') {
	console.log('通过');
} else {
	console.log('未通过');
}

// 答案:

// '通过'

// hint: 
// 加减乘除的优先级高于 && ||
// typeof(a) => 'undefined' | (-true) => -1 | (+ undefined) => NaN

// if 中 => "undefined" && (-1 + NaN) + '' => 
// "undefined" && NaN + '' =>
// "undefined" && 'NaN' => true

if(1 + 5 * '3' === 16) {
	console.log('通过');
} else {
  consoel.log('未通过'); 
}

// 答案:

// '通过'

// hint: 乘法,会把非数字转数字 1 + 5 * '3' => 1 + 15

  

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

// 答案:

// 1

// 分析: 

// !! 是布尔值取两次反, 即 !!true 还是 true
// !!' ' => true | !!'' => false | !!false => false
// hint: ' ': 不是非空串,为真,两次取反,等于没反,最后为真

window.a || (window.a = '1');
console.log(window.a);

// 答案:

// '1'
// ()运算优先级高,所以先运行 window.a = '1' , 然后 window.a 就是真
function test() {
  console.log(a);
  if (false) {
    function a () {}
  }
}
test();
console.log(a);

// 答案:

// undefined 然后 报错 ReferenceError: a is not defined
var a = 0;

console.log('No.1', a);

{
  console.log('No.2', a);
  a = 1;
  console.log('No.3', a);
  function a () {}
  console.log('No.4', a);
  a = 2;
  console.log('No.5', a);
}

console.log('No.6', a);

// 答案:

// No.1 0
// No.2 [Function: a]
// No.3 1
// No.4 1
// No.5 2
// No.6 1

// 分析:

// 1. {} 块作用域中 function a 的 a 变量提前到当前作用域(也就是全局作用域)
// 2. 开始执行 a = 0, 所以 No.1 的 a 是 0
// 3. 开始执行 {} 代码块中的内容,首先把 function a() {} 整体提升到块作用域顶部
// ,此刻块作用域中有了变量 a,所以 No.2 的 a 是 function a() {}
// 4. 接着 a = 1, 就把块作用域中的 function a 改成了 1,所以 No.3 的 a 是 1
// 5. 执行到 function a() {} 这一步时,
// 会把块作用域里的 a 赋值到全局同名变量 a,此刻全局 a 变成了 1
// 6. 接着 a = 2 ,把块作用域的 a 变成了 2,所以 No.5 的 a 是 2
// 7. No.6 打印全局 a ,是 1

/**
 * go: {
 *  a: undefined -> 0 -> 1
 * }
 * 
 * ao: {
 *  a: function a -> 1
 * }
 */
let a = 0;

console.log(a);

if (true) {
  a = 1;

  console.log(a);

  function a() {}

  a = 2;

  console.log(a);
}

console.log(a);

// 答案:

// 0 1 2 0

// 分析:
// 这一题有点不一样,外部 a 是用 let 声明的 ,无法被 function a 覆盖
{
  let a = 1;
  function a() {}
}

// 答案:

// Uncaught SyntaxError: Identifier 'a' has already been declared

// 报错,暂时性死区

⭐️ 作用域

⭐️ arguments