【JavaScript】原生函数

什么是原生函数

原生函数 (Native Functions), 也称为内置函数或内建构造函数, 是 JavaScript 语言内置的全局对象, 用于创建和处理特定类型的数据.

常用原生函数列表

基本类型相关:

  • String - 字符串
  • Number - 数字
  • Boolean - 布尔值
  • Symbol - 符号 (ES6+)
  • BigInt - 任意精度整数 (ES2020+)

引用类型相关:

  • Object - 对象
  • Array - 数组
  • Function - 函数
  • RegExp - 正则表达式
  • Date - 日期
  • Error - 错误对象

注意: nullundefined 虽然是基本类型, 但没有对应的构造函数.

原生函数的两种用法

原生函数可以作为普通函数调用, 也可以作为构造函数使用 (配合 new 关键字).

作为普通函数调用

直接调用原生函数通常用于类型转换:

javascript 复制代码
String(123); // "123"
Number("456"); // 456
Boolean(0); // false

作为构造函数使用

使用 new 关键字会创建对应类型的封装对象:

javascript 复制代码
const str = new String("abc");

typeof str; // "object" (不是 "string")
str instanceof String; // true
Object.prototype.toString.call(str); // "[object String]"

通过构造函数创建的是封装对象 (wrapper object), 而非基本类型值本身.

两者的区别

javascript 复制代码
// 普通调用: 返回基本类型值
const a = String("hello");
typeof a; // "string"

// 构造函数调用: 返回封装对象
const b = new String("hello");
typeof b; // "object"

// 两者的值相等, 但类型不同
a == b; // true (值相等)
a === b; // false (类型不同)

特殊情况

Symbol 和 BigInt 不能使用 new:

javascript 复制代码
// Symbol 不能作为构造函数
const sym = Symbol("description"); // 正确
// const sym = new Symbol("desc");     // TypeError: Symbol is not a constructor

// BigInt 也不能作为构造函数
const big = BigInt(123); // 正确
// const big = new BigInt(123);        // TypeError: BigInt is not a constructor

Array 和 Function 可省略 new:

javascript 复制代码
// 以下两种写法效果相同
const arr1 = new Array(1, 2, 3);
const arr2 = Array(1, 2, 3);

const fn1 = new Function("a", "return a * 2");
const fn2 = Function("a", "return a * 2");

基本类型与封装对象

基本类型值 vs 封装对象

JavaScript 有 7 种基本类型:

  • string
  • number
  • boolean
  • symbol
  • bigint
  • null
  • undefined

基本类型值本身没有属性和方法, 但我们却能调用它们的方法:

javascript 复制代码
const str = "hello";
str.length; // 5
str.toUpperCase(); // "HELLO"

这是因为 JavaScript 引擎会自动进行装箱操作.

自动装箱 (Auto-boxing)

当访问基本类型值的属性或方法时, JavaScript 会自动完成以下步骤:

  1. 创建对应类型的封装对象
  2. 调用封装对象上的方法
  3. 立即销毁该封装对象
javascript 复制代码
// 等价于以下过程:
const str = "hello";
str.toUpperCase();

// 1. 创建临时封装对象
// const temp = new String("hello");
// 2. 调用方法
// temp.toUpperCase();
// 3. 销毁临时对象

重要特性: 封装对象是临时的, 无法为基本类型值添加属性:

javascript 复制代码
const str = "test";
str.customProp = "value";
console.log(str.customProp); // undefined (属性丢失)

拆箱 (Unboxing)

获取封装对象中的基本类型值, 使用 valueOf() 方法:

javascript 复制代码
const strObj = new String("abc");
const numObj = new Number(42);
const boolObj = new Boolean(true);

strObj.valueOf(); // "abc"
numObj.valueOf(); // 42
boolObj.valueOf(); // true

隐式拆箱会在需要基本类型值的上下文中自动发生:

javascript 复制代码
const a = new String("hello");
const b = a + " world"; // 隐式调用 valueOf()

typeof a; // "object"
typeof b; // "string"
b; // "hello world"

使用建议与最佳实践

避免直接使用封装对象:

javascript 复制代码
// 不推荐: 使用封装对象
const str = new String("abc");
const num = new Number(42);

// 推荐: 使用基本类型值
const str = "abc";
const num = 42;

封装对象的陷阱:

javascript 复制代码
// Boolean 封装对象始终为真值
const falseObj = new Boolean(false);

if (falseObj) {
    console.log("执行了!"); // 会执行 (对象是真值)
}

// 基本类型值的行为符合预期
const falsePrimitive = false;

if (falsePrimitive) {
    console.log("不会执行"); // 不会执行
}

最佳实践:

  • 优先使用基本类型值 ("abc", 42, true)
  • 避免使用 new String(), new Number(), new Boolean()
  • 让 JavaScript 引擎自动处理装箱和拆箱
  • 仅在类型转换时直接调用构造函数 (不使用 new)

Object 作为通用包装类构造函数

除了使用 String, Number, Boolean 构造函数创建对应的封装对象, 还可以使用 Object 构造函数, 它会根据参数类型自动返回对应包装类的实例:

javascript 复制代码
// 传入字符串, 返回 String 实例
const strObj = new Object("hello");
strObj instanceof String; // true
typeof strObj; // "object"

// 传入数值, 返回 Number 实例
const numObj = new Object(42);
numObj instanceof Number; // true

// 传入布尔值, 返回 Boolean 实例
const boolObj = new Object(true);
boolObj instanceof Boolean; // true

这种方式虽然可行, 但不推荐使用, 原因:

  1. 代码意图不明确 (无法直接看出要创建哪种类型)
  2. 性能略低于直接使用对应构造函数
  3. 不符合现代 JavaScript 最佳实践

注意: nullundefined 没有对应的包装类, 传入 Object 构造函数会返回空对象:

javascript 复制代码
const nullObj = new Object(null);
const undefinedObj = new Object(undefined);

nullObj; // {}
undefinedObj; // {}

对象类型检测

typeof 的局限性

typeof 对于对象类型的检测能力有限:

javascript 复制代码
typeof []; // "object" (无法区分数组)
typeof null; // "object" (历史遗留 bug)
typeof new Date(); // "object"
typeof /regex/; // "object"
typeof new String("abc"); // "object"

Object.prototype.toString() 方法

更准确的类型检测方式是使用 Object.prototype.toString():

javascript 复制代码
Object.prototype.toString.call([1, 2, 3]); // "[object Array]"
Object.prototype.toString.call(/regex/i); // "[object RegExp]"
Object.prototype.toString.call(new Date()); // "[object Date]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"

基本类型值的自动装箱:

javascript 复制代码
// 基本类型值会被自动包装
Object.prototype.toString.call("abc"); // "[object String]"
Object.prototype.toString.call(42); // "[object Number]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(Symbol()); // "[object Symbol]"
Object.prototype.toString.call(123n); // "[object BigInt]"

Symbol.toStringTag

ES6 引入了 Symbol.toStringTag, 允许自定义对象的类型标签:

javascript 复制代码
// 默认行为
class MyClass {}
Object.prototype.toString.call(new MyClass()); // "[object Object]"

// 使用 Symbol.toStringTag 自定义
class MyCustomClass {
    get [Symbol.toStringTag]() {
        return "MyCustomClass";
    }
}

Object.prototype.toString.call(new MyCustomClass()); // "[object MyCustomClass]"

内置对象的 Symbol.toStringTag:

JavaScript 内置对象 (如 Map, Set, Promise) 内部也定义了 Symbol.toStringTag 属性, 这就是 Object.prototype.toString() 能够识别它们类型的原因:

javascript 复制代码
// 内置对象的 Symbol.toStringTag 属性
const map = new Map();
map[Symbol.toStringTag]; // "Map"

const set = new Set();
set[Symbol.toStringTag]; // "Set"

const promise = Promise.resolve();
promise[Symbol.toStringTag]; // "Promise"

// toString() 方法读取这个属性来生成类型字符串
Object.prototype.toString.call(map); // "[object Map]"
Object.prototype.toString.call(set); // "[object Set]"
Object.prototype.toString.call(promise); // "[object Promise]"

自定义对象使用 Symbol.toStringTag:

我们可以在自定义类中使用相同的机制, 让对象有更友好的类型标识:

javascript 复制代码
class Collection {
    constructor(items) {
        this.items = items;
    }

    get [Symbol.toStringTag]() {
        return "Collection";
    }
}

const col = new Collection([1, 2, 3]);

// 自定义的类型标签
col[Symbol.toStringTag]; // "Collection"

// toString() 会使用这个标签
Object.prototype.toString.call(col); // "[object Collection]"

// 对比: 没有定义 Symbol.toStringTag 的普通类
class NormalClass {}
const normal = new NormalClass();
Object.prototype.toString.call(normal); // "[object Object]"

注意事项:

由于 Symbol.toStringTag 可以被随意修改, 不应完全依赖 toString() 进行类型检测:

javascript 复制代码
const fakeArray = {
    get [Symbol.toStringTag]() {
        return "Array";
    },
};

Object.prototype.toString.call(fakeArray); // "[object Array]"
Array.isArray(fakeArray); // false (更可靠)

原生原型

原型对象的特性

每个原生构造函数都有对应的 prototype 对象, 包含该类型的方法和属性.

javascript 复制代码
// 字符串方法来自 String.prototype
String.prototype.toUpperCase; // [Function: toUpperCase]
"hello".toUpperCase === String.prototype.toUpperCase; // true

// 数组方法来自 Array.prototype
Array.prototype.map; // [Function: map]
[1, 2, 3].map === Array.prototype.map; // true

特殊的原生原型: 某些原生原型本身就是对应类型的实例.

javascript 复制代码
// Function.prototype 是一个函数
typeof Function.prototype; // "function"
Function.prototype(); // undefined (空函数)

// Array.prototype 是一个数组
Array.isArray(Array.prototype); // true
Array.prototype.length; // 0

// RegExp.prototype 是一个正则表达式
RegExp.prototype.toString(); // "/(?:)/"
"test".match(RegExp.prototype); // [""]

修改原型的风险

虽然可以修改原生原型, 但这是强烈不推荐的做法:

javascript 复制代码
// 不推荐: 修改原生原型
Array.prototype.customMethod = function () {
    return "custom";
};

[1, 2, 3].customMethod(); // "custom"

// 风险 1: 影响所有数组
const arr1 = [1, 2];
const arr2 = [3, 4];
arr1.customMethod === arr2.customMethod; // true (共享)

// 风险 2: 可能与未来标准冲突
// 如果 ES 未来添加同名方法, 会导致不兼容

// 风险 3: 影响 for...in 遍历
for (let key in [1, 2, 3]) {
    console.log(key); // "0", "1", "2", "customMethod"
}

可修改原型的例外情况:

javascript 复制代码
// Polyfill: 为旧浏览器添加标准方法
if (!Array.prototype.includes) {
    Array.prototype.includes = function (searchElement) {
        return this.indexOf(searchElement) !== -1;
    };
}

现代最佳实践

不要使用原生原型作为默认值:

javascript 复制代码
// 不推荐 (旧做法)
function oldWay(vals, fn, rx) {
    vals = vals || Array.prototype;
    fn = fn || Function.prototype;
    rx = rx || RegExp.prototype;

    return rx.test(vals.map(fn).join(""));
}

// 推荐 (使用默认参数)
function modernWay(vals = [], fn = (v) => v, rx = /.*/) {
    return rx.test(vals.map(fn).join(""));
}

// 或者使用解构赋值
function betterWay({ vals = [], fn = (v) => v, rx = /.*/ } = {}) {
    return rx.test(vals.map(fn).join(""));
}

原因:

  1. ES6+ 默认参数更清晰, 性能更好
  2. 避免意外修改原生原型
  3. 代码可读性更强

不要修改内置对象的原型:

javascript 复制代码
// 不推荐: 扩展内置对象
String.prototype.reverse = function () {
    return this.split("").reverse().join("");
};

// 推荐: 使用独立函数
function reverseString(str) {
    return str.split("").reverse().join("");
}

// 或者使用工具类
class StringUtils {
    static reverse(str) {
        return str.split("").reverse().join("");
    }
}
相关推荐
533_4 小时前
[vue] dayjs 显示实时时间
前端·javascript·vue.js
CoderCodingNo4 小时前
【GESP】C++五级考试大纲知识点梳理, (5) 算法复杂度估算(多项式、对数)
开发语言·c++·算法
ftpeak4 小时前
JavaScript性能优化实战
开发语言·javascript·性能优化
一个很帅的帅哥5 小时前
JavaScript事件循环
开发语言·前端·javascript
驰羽5 小时前
[GO]gin框架:ShouldBindJSON与其他常见绑定方法
开发语言·golang·gin
程序员大雄学编程5 小时前
「用Python来学微积分」5. 曲线的极坐标方程
开发语言·python·微积分
云枫晖5 小时前
Webapck系列-初识Webpack
前端·javascript
jiangzhihao05156 小时前
升级到webpack5
前端·javascript·vue.js
哆啦A梦15886 小时前
36 注册
前端·javascript·html