JavaScript函数与对象增强知识

JavaScript 函数与对象:从入门到进阶的核心知识点详解

如果你正在学习 JavaScript,或者想要系统性地提升自己的前端开发能力,那么这篇文章绝对值得你认真阅读。我们将深入探讨 JavaScript 中两个最重要的概念------函数和对象的高级特性,这些知识点在实际开发中无处不在。

一、引言

JavaScript 作为一门「面向对象」且「函数式编程」的语言,函数和对象是它的两大基石。几乎所有的前端框架(Vue、React、Angular)都离不开对这两个概念的深入运用。很多开发者在学习初期只是掌握了基本的语法,但当项目复杂度提升时,往往会发现自己的知识储备不够用。

这篇文章,我们将从「函数的增强知识」和「对象的增强知识」两个维度,系统梳理那些面试中常考、开发中常用,但容易被忽视的核心知识点。


二、函数的增强知识

2.1 函数对象:函数也是对象

在 JavaScript 中,函数是一等公民。这意味着函数本身也是对象,拥有自己的属性和方法。

javascript 复制代码
function greet(name) {
  return `Hello, ${name}!`;
}

console.log(greet.length); // 1 ------ 形参个数
console.log(greet.name);   // "greet" ------ 函数名
console.log(greet.prototype); // { constructor: f } ------ 原型对象
console.log(typeof greet.toString); // "function" ------ 方法

理解这一点非常重要:你可以把函数当作普通的对象来传递、赋值、作为参数或返回值。这为 JavaScript 的高阶函数编程奠定了基础。

2.2 arguments 对象:灵活的参数容器

在 ES6 之前,arguments 对象是获取函数所有实参的唯一方式:

javascript 复制代码
function sum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

console.log(sum(1, 2, 3, 4, 5)); // 15

arguments 的特点:

  • 是一个类数组对象(Array-like Object),不是真正的数组
  • 包含所有传入的参数
  • 在箭头函数中不可用(箭头函数没有自己的 arguments)

2.3 剩余参数(Rest Parameters):现代解决方案

ES6 引入了更优雅的 ...rest 语法来解决参数收集问题:

javascript 复制代码
function sum(...numbers) {
  return numbers.reduce((acc, curr) => acc + curr, 0);
}

console.log(sum(1, 2, 3, 4, 5)); // 15

剩余参数 vs arguments:

特性 Rest Parameters arguments
类型 真正的数组 类数组对象
箭头函数可用
可选参数 ✅(可在中间使用) ❌(只能放在最后)
ES6+ 推荐

2.4 函数的 prototype:继承的基石

每个函数都有一个 prototype 属性,它指向一个包含 constructor 属性的对象。当使用 new 关键字创建实例时,实例会继承 prototype 上的属性和方法。

javascript 复制代码
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function() {
  return `Hi, I'm ${this.name}`;
};

const alice = new Person('Alice', 25);
console.log(alice.sayHello()); // "Hi, I'm Alice"
console.log(alice instanceof Person); // true

2.5 改变 this 的指向:call、apply、bind

这是 JavaScript 中最核心也最容易混淆的知识点之一。

call 方法

call() 方法调用一个函数,并指定 this 的值和单独给出的参数。

javascript 复制代码
const person1 = { name: 'Alice' };
const person2 = { name: 'Bob' };

function greet(greeting, punctuation) {
  return `${greeting}, I'm ${this.name}${punctuation}`;
}

console.log(greet.call(person1, 'Hello', '!')); // "Hello, I'm Alice!"
console.log(greet.call(person2, 'Hi', '~'));    // "Hi, I'm Bob~"
apply 方法

apply()call() 类似,但参数以数组形式传递。

javascript 复制代码
const numbers = [3, 1, 4, 1, 5, 9, 2, 6];

console.log(Math.max.apply(null, numbers)); // 9
console.log(Math.min.apply(null, numbers)); // 1
bind 方法

bind() 方法创建一个新的函数,当被调用时,其 this 关键字指向指定的值。

javascript 复制代码
const module = {
  x: 42,
  getX: function() {
    return this.x;
  }
};

const unboundGetX = module.getX;
console.log(unboundGetX()); // undefined(在严格模式或浏览器中是 undefined)

const boundGetX = unboundGetX.bind(module);
console.log(boundGetX()); // 42

实战场景:

javascript 复制代码
// 场景一:数组方法借用
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
const arr = Array.prototype.slice.call(arrayLike);
console.log(arr); // ['a', 'b', 'c']

// 场景二:事件处理中的 this 绑定
class Button {
  constructor() {
    this.clicked = false;
    // 确保回调中的 this 指向实例
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    this.clicked = !this.clicked;
    console.log('Button clicked:', this.clicked);
  }
}

2.6 函数作为参数和返回值

JavaScript 函数的强大之处在于它可以作为参数传递或作为返回值返回。

javascript 复制代码
// 函数作为参数(回调函数)
function fetchData(url, callback) {
  // 模拟异步请求
  setTimeout(() => {
    callback({ success: true, data: 'Some data' });
  }, 1000);
}

fetchData('/api/users', function(response) {
  console.log(response);
});

// 函数作为返回值(工厂函数)
function createMultiplier(factor) {
  return function(number) {
    return number * factor;
  };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

2.7 立即执行函数表达式(IIFE)

IIFE(Immediately Invoked Function Expression)是一种在定义后立即执行的函数,常用于创建独立的作用域,避免变量污染全局命名空间。

javascript 复制代码
// 经典写法
(function() {
  const privateVar = 'I am private';
  console.log(privateVar);
})();

// 现代写法(箭头函数)
(() => {
  const message = 'Hello from IIFE';
  console.log(message);
})();

// 带参数
((name) => {
  console.log(`Hello, ${name}!`);
})('World');

// IIFE 的典型应用:模块化
const Counter = (function() {
  let count = 0; // 私有变量
  
  return {
    increment: function() {
      count++;
      return count;
    },
    decrement: function() {
      count--;
      return count;
    },
    getCount: function() {
      return count;
    }
  };
})();

Counter.increment();
Counter.increment();
console.log(Counter.getCount()); // 2

2.8 闭包:JavaScript 的魔法

闭包是指函数能够记住并访问其词法作用域,即使函数在其词法作用域之外被执行。

javascript 复制代码
function createCounter(initialValue = 0) {
  let count = initialValue;
  
  return {
    increment: function() {
      count++;
      return count;
    },
    decrement: function() {
      count--;
      return count;
    },
    reset: function() {
      count = initialValue;
      return count;
    }
  };
}

const counter1 = createCounter(10);
const counter2 = createCounter();

console.log(counter1.increment()); // 11
console.log(counter1.increment()); // 12
console.log(counter2.increment()); // 1
console.log(counter2.decrement()); // 0

闭包的经典面试题:

javascript 复制代码
// 错误示例
for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 输出 3, 3, 3
  }, 100);
}

// 正确解决方案
// 方案一:使用 let(块级作用域)
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 输出 0, 1, 2
  }, 100);
}

// 方案二:使用闭包(ES5 兼容)
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j); // 输出 0, 1, 2
    }, 100);
  })(i);
}

三、对象的增强知识

3.1 属性描述符:精准控制对象属性

JavaScript 提供了 Object.defineProperty() 方法,允许我们对属性的特性进行精细控制。

javascript 复制代码
const person = {};

// 定义一个只读属性
Object.defineProperty(person, 'name', {
  value: 'Alice',
  writable: false,      // 不可修改
  enumerable: true,     // 可枚举
  configurable: false  // 不可删除或重新定义
});

console.log(person.name); // "Alice"
person.name = 'Bob';      // 在非严格模式下静默失败
console.log(person.name); // 仍然是 "Alice"

3.2 属性描述符的两种类型

数据属性描述符(Data Descriptor)
特性 说明
configurable 属性是否可以通过 delete 删除,是否可以修改描述符
enumerable 属性是否可以通过 for...inObject.keys() 枚举
writable 属性的值是否可以被修改
value 属性的值
javascript 复制代码
const obj = {};
Object.defineProperty(obj, 'id', {
  value: 1001,
  writable: true,
  enumerable: true,
  configurable: true
});
console.log(obj.id); // 1001
存取属性描述符(Accessor Descriptor)
特性 说明
configurable 属性是否可以通过 delete 删除
enumerable 属性是否可以被枚举
get 读取属性时调用的函数
set 写入属性时调用的函数

存取属性描述符的典型应用:

javascript 复制代码
class Temperature {
  constructor(celsius) {
    this._celsius = celsius;
  }
  
  get fahrenheit() {
    return this._celsius * 9/5 + 32;
  }
  
  set fahrenheit(value) {
    this._celsius = (value - 32) * 5/9;
  }
}

const temp = new Temperature(25);
console.log(temp.fahrenheit); // 77
temp.fahrenheit = 86;
console.log(temp._celsius);   // 30

数据属性描述符与存取属性描述符对比:

特性 数据描述符 存取描述符
configurable ✅ 可设置 ✅ 可设置
enumerable ✅ 可设置 ✅ 可设置
value ✅ 可设置 ❌ 不可设置
writable ✅ 可设置 ❌ 不可设置
get ❌ 不可设置 ✅ 可设置
set ❌ 不可设置 ✅ 可设置

3.3 Object.defineProperties:批量定义属性

当你需要同时定义多个属性时,可以使用 Object.defineProperties()

javascript 复制代码
const product = {};

Object.defineProperties(product, {
  name: {
    value: 'Laptop',
    writable: true,
    enumerable: true,
    configurable: true
  },
  price: {
    value: 9999,
    writable: true,
    enumerable: true,
    configurable: true
  },
  discount: {
    get: function() {
      return this._discount || 0;
    },
    set: function(value) {
      if (value < 0 || value > 100) {
        throw new Error('折扣必须在0-100之间');
      }
      this._discount = value;
    },
    enumerable: true,
    configurable: true
  }
});

product.discount = 15;
console.log(product.name, product.price, product.discount); // Laptop 9999 15

3.4 获取属性描述符

javascript 复制代码
const person = { name: 'Alice', age: 25 };

console.log(Object.getOwnPropertyDescriptor(person, 'name'));
// { value: 'Alice', writable: true, enumerable: true, configurable: true }

console.log(Object.getOwnPropertyDescriptors(person));
// { name: {...}, age: {...} }

3.5 对象方法补充

preventExtensions:禁止扩展
javascript 复制代码
const obj = { a: 1 };
Object.preventExtensions(obj);
obj.b = 2;  // 严格模式下抛出 TypeError
console.log('b' in obj); // false
seal:密封对象
javascript 复制代码
const obj = { a: 1 };
Object.seal(obj);
obj.a = 2;          // 可以修改
// obj.b = 2;       // 不可添加(严格模式报错)
// delete obj.a;    // 不可删除(严格模式报错)
console.log(Object.isSealed(obj)); // true
freeze:冻结对象
javascript 复制代码
const CONFIG = { apiUrl: 'https://api.example.com', timeout: 5000 };
Object.freeze(CONFIG);

// CONFIG.apiUrl = 'another'; // 静默失败
// CONFIG.newProp = 'value';   // 静默失败
// delete CONFIG.timeout;     // 静默失败

console.log(Object.isFrozen(CONFIG)); // true

三者的对比:

方法 禁止添加属性 禁止删除属性 禁止修改属性值
preventExtensions
seal
freeze

四、实际应用场景与最佳实践

4.1 数据绑定与响应式系统

属性描述符是实现响应式数据绑定的核心技术,Vue 2 的响应式系统就是基于 Object.defineProperty 实现的:

javascript 复制代码
function defineReactive(obj, key, value) {
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function() {
      console.log(`读取属性 ${key}`);
      return value;
    },
    set: function(newValue) {
      if (newValue !== value) {
        console.log(`设置属性 ${key}: ${value} -> ${newValue}`);
        value = newValue;
        // 触发更新逻辑
      }
    }
  });
}

const data = {};
defineReactive(data, 'message', 'Hello');
console.log(data.message); // 触发 getter
data.message = 'World';    // 触发 setter

4.2 私有属性实现

在 ES6 class 之前,利用闭包和属性描述符可以实现私有属性:

javascript 复制代码
function BankAccount(initialBalance) {
  // 私有变量
  let balance = initialBalance;
  let accountNumber = Math.random().toString(36).substr(2, 9);
  
  // 公共方法通过闭包访问私有变量
  this.getBalance = function() {
    return balance;
  };
  
  this.deposit = function(amount) {
    if (amount > 0) {
      balance += amount;
      return true;
    }
    return false;
  };
  
  this.withdraw = function(amount) {
    if (amount > 0 && amount <= balance) {
      balance -= amount;
      return true;
    }
    return false;
  };
  
  // 定义只读属性
  Object.defineProperty(this, 'accountNumber', {
    get: function() {
      return accountNumber;
    },
    enumerable: true
  });
}

const account = new BankAccount(1000);
console.log(account.getBalance()); // 1000
account.deposit(500);
console.log(account.getBalance()); // 1500
console.log(account.accountNumber); // 随机生成的账号

4.3 配置对象的验证

javascript 复制代码
function createButton(options) {
  const defaultOptions = {
    text: 'Click me',
    color: '#333',
    backgroundColor: '#fff',
    borderRadius: 4,
    disabled: false,
    onClick: function() {}
  };
  
  // 合并配置
  const config = Object.assign({}, defaultOptions, options);
  
  // 验证配置
  if (typeof config.text !== 'string' || config.text.length === 0) {
    throw new Error('按钮文本不能为空');
  }
  
  if (config.borderRadius < 0 || config.borderRadius > 50) {
    config.borderRadius = Math.max(0, Math.min(50, config.borderRadius));
  }
  
  // 创建按钮元素
  const button = document.createElement('button');
  button.textContent = config.text;
  button.style.color = config.color;
  button.style.backgroundColor = config.backgroundColor;
  button.style.borderRadius = `${config.borderRadius}px`;
  button.disabled = config.disabled;
  button.onclick = config.onClick;
  
  return button;
}

4.4 不可变配置的最佳实践

javascript 复制代码
// 应用配置应该被冻结
const APP_CONFIG = Object.freeze({
  apiBaseUrl: 'https://api.example.com',
  version: '1.0.0',
  features: Object.freeze({
    darkMode: true,
    notifications: true,
    analytics: false
  })
});

// 数据库配置也应该冻结
const DB_CONFIG = Object.freeze({
  host: 'localhost',
  port: 5432,
  database: 'myapp',
  pool: Object.freeze({
    min: 2,
    max: 10
  })
});

函数部分

  • ✅ 函数是一等公民,可以作为参数、返回值、赋值给变量
  • arguments 对象提供参数访问(已被 rest parameters 取代)
  • prototype 是实现继承的关键
  • call/apply/bind 改变函数执行上下文
  • ✅ IIFE 创建独立作用域,避免全局污染
  • ✅ 闭包是 JavaScript 最强大的特性之一

对象部分

  • Object.defineProperty 实现精细的属性控制
  • ✅ 数据描述符和存取描述符各有适用场景
  • Object.defineProperties 批量定义属性
  • preventExtensions/seal/freeze 实现不同级别的对象保护
  • ✅ 属性描述符是实现响应式系统的基础

掌握了这些知识点,你将在以下场景中游刃有余:

  • 编写更优雅、更高效的 JavaScript 代码
  • 理解和阅读 Vue/React 等框架的源码
  • 面试中应对各种高级 JavaScript 问题
  • 设计自己的工具库和框架

希望这篇文章对你有帮助!如果你觉得有用,欢迎转发给身边学习 JavaScript 的朋友。如果有任何问题或建议,欢迎在评论区留言交流。

相关推荐
IGAn CTOU3 小时前
Java高级开发进阶教程之系列
java·开发语言
csbysj20203 小时前
SQL NULL 函数详解
开发语言
其实防守也摸鱼3 小时前
CTF密码学综合教学指南--第三章
开发语言·网络·python·安全·网络安全·密码学
NGSI vimp3 小时前
Java进阶——如何查看Java字节码
java·开发语言
We་ct4 小时前
深度剖析浏览器跨域问题
开发语言·前端·浏览器·跨域·cors·同源·浏览器跨域
skywalk81634 小时前
在考虑双轨制,即在中文语法的基础上,加上数学公式的支持,这样像很多计算将更加简单方便,就像现在的小学数学课本里面一样,比如:定x=2*x + 1
开发语言
小书房4 小时前
Kotlin的by
android·开发语言·kotlin·委托·by
就叫飞六吧4 小时前
QT写一个桌面程序exe并动态打包基本流程(c++)
开发语言·c++
threelab4 小时前
Three.js 代码云效果 | 三维可视化 / AI 提示词
开发语言·javascript·人工智能