JavaScript 基础知识

IIFE(立即调用函数表达式)

IIFE(Immediately Invoked Function Expression)是定义完立刻执行的函数表达式。

它不是一种新语法,而是「写成表达式的函数 + 后面紧跟调用括号」。

(function () {
    const name = "Lizhao";
    return name;
})();
func; // "Lizhao"

赋给变量的不是函数本身,而是这次调用的返回值。上面的 func 是字符串 "Lizhao",不能再当函数调用。

执行时会新建一个函数作用域。里面的 var、函数声明出不去,从而避免污染外层(在 letconst 和 ES 模块普及以前,这是隔离作用域的主要手段)。

特性

  • 内部用 var 声明的变量,外面访问不到。
  • 赋给变量时,存的是返回值,不是函数。
  • 每次写出并执行一次 IIFE,就是一次独立的作用域;不会自动「再执行一遍」,除非代码再次跑到这句。

closure(闭包)

闭包(closure)是一个函数里面嵌套另外一个函数,被嵌套(内部)的函数自身形成了一个闭包,这个闭包可以自己拥有独立的词法环境,也就意味着,被嵌套函数的作用域除了自身的作用域,还维持了其容器函数的作用域。

一句话概括: 闭包就是一个拥有自身作用域和父级作用域(由外层函数提供,即便外层函数销毁依旧可以访问)的特殊函数。

注意: 闭包的本质是利用了作用域的机制,来达到外部作用域访问内部作用域的目的。

闭包有以下特性:

  • 闭包是一个函数,它是另一个函数(称容器函数)的执行结果。
  • 闭包可以拥有其容器函数的作用域。
const func = function () {
    const name = "Lizhao";
    return function () {
        return name;
    };
};

const closureFunc = func();
closureFunc(); // "Lizhao"

闭包不是「外层去读内层变量」,而内层函数被暴露到外面之后,外面通过调用这个函数,间接用到内层还记着的外层变量

闭包会延长变量寿命。内部函数一直活着,它引用的外层变量就不会回收;大对象、DOM 节点被闭包抓住,就是常见泄漏路径。

Object.keys 返回的属性顺序

现行引擎里,普通对象的 Object.keys 顺序是确定的,可以按规范来预期。 它只返回对象自身的、可枚举的字符串键,不含 Symbol,也不含原型链上的键。

顺序与 [[OwnPropertyKeys]] 里字符串那两段一致:

  • 数组索引键:值为规范说的 array index(02^32 - 2 的整数,且键名是该整数的规范字符串形式),按数值从小到大
  • 其余字符串键(含 '3.1''-1''01'、过长数字串等):按属性创建先后

Object.keys 不会列出 Symbol 键。要连 Symbol 一起看,用 Reflect.ownKeys(字符串规则同上,最后再按创建顺序追加 Symbol)或 Object.getOwnPropertySymbols

const symbol1 = Symbol("symbol1");
const symbol2 = Symbol("symbol2");
const obj = {
  d: "d",
  [symbol1]: "symbol1",
  a: "a",
  c: "c",
  b: "b",
  1: 1,
  "3.1": 3.1,
  2: 2,
  [symbol2]: "symbol2",
};

Object.keys(obj);
// ["1", "2", "d", "a", "c", "b", "3.1"]
// 整数索引 1、2 在前;其余字符串按写入顺序;两个 Symbol 不出现

Reflect.ownKeys(obj);
// ["1", "2", "d", "a", "c", "b", "3.1", symbol1, symbol2]

对象字面量里写成 symbol1: "symbol1"(没有方括号)时,键是字符串 "symbol1",不是上面的 Symbol。旧示例把这两种写法混在一起,注释里的顺序不能当作 Symbol 键的行为。

for...in 的字符串键顺序与 Object.keys 相同,但 for...in 还会继续走原型链上可枚举的字符串键。两者都不是「随便排」,也不是「完全一样的一份列表」。

变量提升

变量提升(hoisting)指:进入一个作用域时,引擎先处理该作用域里的声明,再从上到下执行语句。看起来像声明被「挪到了顶部」,赋值和函数体里的语句仍留在原处。

var 只提升声明,初始化相当于 undefined。赋值留在原来那一行:

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

等价于:

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

函数声明:具名 function foo() {} 会把整个函数提升到当前作用域顶部(函数体一起上去)。函数表达式、箭头函数只提升左边的绑定(若是 var),右边的函数对象要等执行到那一行才创建:

foo(); // 能调用,函数声明已提升

function foo() {
    return 1;
}

bar(); // TypeError: bar is not a function
var bar = function () {
    return 2;
};

同一作用域里同名时

函数声明先就位;随后的 var 同名不会把函数清掉,只是重复声明被忽略。赋值仍按源码顺序执行,所以后面的 var c = 1 会把函数盖掉:

var c = 1;
function c(c) {
    console.log(c);
    var c = 3;
}
c(2);
// TypeError: c is not a function

预解析后相当于:

function c(c) {
  console.log(c);
  var c = 3;
}
var c; // 与上面的函数同名,声明被忽略
c = 1; // 把函数换成数字 1
c(2); // 对 1 做调用 → TypeError

letconstclass也会在进入作用域时被创建,但在初始化之前处于暂时性死区(TDZ):提前访问会 ReferenceError,不是 undefined。不要把这理解成「和 var 一样提升后就能用」。

console.log(x); // ReferenceError
let x = 1;

© lizhao all right reserved,powered by Gitbook文件修订时间: 2026-08-26 20:48:19

results matching ""

    No results matching ""