面向C++程序员的JavaScript 语法实战学习4

面向C++程序员的JavaScript教程学习

目录

  1. [定义函数(Function 的几种写法)](#定义函数(Function 的几种写法))
  2. [调用函数(调用方式与 this)](#调用函数(调用方式与 this))
  3. [函数作用域(var / let / const / 块作用域)](#函数作用域(var / let / const / 块作用域))
  4. [作用域与函数栈(执行上下文与 call stack)](#作用域与函数栈(执行上下文与 call stack))
  5. 闭包(Closures)
  6. [使用 arguments 对象](#使用 arguments 对象)
  7. 函数参数(默认参数、剩余参数、参数解构)
  8. 箭头函数(与普通函数的区别)
  9. [预定义 / 标准内建函数(Global objects & functions)](#预定义 / 标准内建函数(Global objects & functions))

定义函数

JS中,定义函数非常简单,因为缺少类型,或者说,类型是自己约定的,模板这样套就行:

  1. 函数声明(Function Declaration)

    js 复制代码
    function add(a, b) {
      return a + b;
    }

    函数声明会被提升(hoisting),整个函数体在运行前就可被调用。所以只要在文件中编写了,这个文件内到处都可以调用。

  2. 函数表达式(Function Expression)

    js 复制代码
    const mul = function(a, b) {
      return a * b;
    };

    函数表达式在运行到该语句时才会被赋值,不同于声明的提升行为。

  3. 箭头函数(=>)(详见后文):

    js 复制代码
    const square = x => x * x;

调用函数

函数是一个具备子功能的一个方法集合,调用函数本质上可以理解为调用一个函数,函数可以出现在全局的普通的函数,可以是对象方法,也可以是构造函数。

  1. 直接调用(普通函数调用)

    js 复制代码
    function foo() { console.log(this); }
    foo(); // 严格模式下 this === undefined,非严格模式下指向全局对象(浏览器为 window)
  2. 作为对象方法调用

    js 复制代码
    const obj = { method() { console.log(this === obj); } };
    obj.method(); // this 指向 obj
  3. 构造函数调用(new)

    js 复制代码
    function Person(name) { this.name = name; }
    const p = new Person('Alice'); // 新对象作为 this
  4. call / apply / bind (显式设置 this

    js 复制代码
    function greet(greeting) { console.log(greeting + ', ' + this.name); }
    greet.call({name:'Bob'}, 'Hi');
    greet.apply({name:'Bob'}, ['Hello']);
    const bound = greet.bind({name:'Carol'});
    bound('Hey');

函数作用域

要点

  • JavaScript 使用词法(静态)作用域:函数定义的位置决定了它访问哪些外部变量。
  • var 创建函数作用域(或全局),let / const 创建块级作用域(block scope)。

示例:varlet

js 复制代码
if (true) {
  var x = 1;   // 在整个外层函数或全局可见
  let y = 2;   // 仅在该块内可见
}
console.log(x); // 1
console.log(y); // ReferenceError

常见坑

  • 使用 var 在循环里创建闭包时会遇到"共享同一变量"的问题,通常用 let 或 IIFE(立即执行函数表达式)解决。

作用域和函数栈(执行上下文 / call stack)

我们知道,C++的函数调用后,咱们的CPU会压入栈的参数保存,栈向下生长,这里的JS也是类似的,对了,还记得使用let定义吗? let在这里就像自动栈上变量一样,比起来,var就会泄漏到全局,还是不太友善的。

要点

  • 每次函数执行会创建一个 执行上下文(execution context) ,包含变量环境、作用域链、this 等信息。多个执行上下文按 LIFO(后进先出)方式放在 调用栈(call stack) 中。

简单演示

text 复制代码
global() // push global context
 -> foo() // push foo context
    -> bar() // push bar context
    <- bar returns // pop bar
 <- foo returns // pop foo

调试建议

在浏览器 DevTools 调试时,可在 call stack 窗口查看当前执行路径(尤其在异常或递归时)。


闭包(Closures)

闭包看起来就像lambda表达式,这里说的就是一个封闭的执行环境。闭包是函数与其词法环境的组合 ------ 内部函数可以"记住"并访问外部函数作用域的变量,即使外部函数已经返回。MDN Closures 解释。

经典示例:计数器

js 复制代码
function makeCounter() {
  let count = 0;
  return function() {
    count += 1;
    return count;
  };
}
const c = makeCounter();
c(); // 1
c(); // 2

内部函数保持对 count 的引用 ------ count 不会被垃圾回收,直到没有引用该闭包为止。

用途
  • 数据封装(模拟私有变量)
  • 部分应用(partial application)
  • 函数工厂

使用 arguments 对象

要点

  • arguments 是非箭头函数内部可用的类数组(array-like)对象 ,包含被调用时传入的所有参数。现代代码更推荐使用 剩余参数(rest parameters)...args)。MDN:arguments 对象。
js 复制代码
function sum() {
  // 把 arguments 转为真正数组
  const args = Array.from(arguments);
  return args.reduce((s, v) => s + v, 0);
}
sum(1,2,3); // 6

// 推荐写法(rest)
function sum2(...nums) {
  return nums.reduce((s, v) => s + v, 0);
}

函数参数

要点与模式

  1. 位置参数(Positional):按顺序传递。

  2. 默认参数(Default parameters)

    js 复制代码
    function greet(name = 'Guest') { console.log('Hello', name); }

    MDN:Default parameters。 (MDN Web 文档)

  3. 剩余参数(Rest parameters)

    js 复制代码
    function f(a, b, ...rest) { /* rest 是数组 */ }
  4. 参数解构(Destructuring)

    js 复制代码
    function draw({x = 0, y = 0} = {}) { /* ... */ }

传参语义

  • 原始类型按值传递(copy 值);
  • 对象类型传递的是引用的副本:函数内修改对象的属性会影响外部对象(因为引用指向同一个对象),但把参数重新赋值不会影响外部引用。

箭头函数(Arrow functions)

要点

  • 语法更简洁:(a, b) => a + b
  • 没有自己的 thisargumentssupernew.target ,箭头函数的 this 来自其词法环境(定义时所在的 this)。MDN:Arrow function expressions。
  • 不能作为构造函数(不能用 new)。
js 复制代码
const obj = {
  value: 42,
  regular() { console.log(this.value); },   // this 指向 obj
  arrow: () => { console.log(this.value); } // this 来自外层(通常不是 obj)
};
obj.regular(); // 42
obj.arrow();   // undefined(取决于外层 this)

何时使用

  • 适合短的回调函数(比如 map/filter),或想要"继承"外层 this 的场景(例如在类方法内部的回调)。
  • 不适合需要动态 this 或作为构造器的情形。

推荐 MDN 参考(快速链接)


相关推荐
夫唯不争,故无尤也1 小时前
Python广播机制:张量的影分身术
开发语言·python
万少1 小时前
上架元服务-味寻纪 技术分享
前端·harmonyos
qq_479875431 小时前
X-Macros(3)
java·开发语言
想不明白的过度思考者1 小时前
Spring Web MVC从入门到实战
java·前端·spring·mvc
列逍1 小时前
深入理解 C++ 异常:从概念到实战的全面解析
开发语言·c++
wdfk_prog1 小时前
[Linux]学习笔记系列 -- [kernel]trace
linux·笔记·学习
java1234_小锋1 小时前
简述Mybatis的插件运行原理?
java·开发语言·mybatis
AAA简单玩转程序设计2 小时前
C++进阶小技巧:让代码从"能用"变"优雅"
前端·c++
子洋2 小时前
群晖 DSM 更新后 Cloudflare DDNS 失效的排查记录
前端·后端·dns