JavaScript对象与函数核心概念详解

作者:拾光

时间:2026-10-29

浏览:212

本文深入解析JavaScript中的对象与函数。涵盖简单类型与对象的区分、对象字面量创建与属性访问、原型继承机制。重点讲解函数的四种调用模式(方法、函数、构造函数、apply)及其对this绑定的影响,并介绍基于原型的继承与模块模式实现私有成员保护。

文章目录

JavaScript对象与函数核心概念详解

阅读本教程前,建议具备其他编程语言的开发经验。

JavaScript中的基础数据类型包括:

1. 数字类型 2. 字符串类型 3. 布尔类型(包含 true 和 false) 4. null 类型 5. undefined 类型

除上述基础类型外,其余所有类型在JavaScript中均被视为对象(注意不要被 typeof 操作符的返回值误导)。具体示例包括:

1. 函数 2. 数组 3. 正则表达式 4. 对象本身(对象自然属于对象)

对象基础与原型机制

对象基础概念

在JavaScript中,对象本质上是属性的集合(可视为关联数组)。每个属性由两部分组成:

1. 属性名:必须为字符串类型 2. 属性值:可以是除 undefined 以外的任意值

可以通过对象字面量语法来创建对象:

代码如下:

// 使用对象字面量 {} 创建空对象
var empty_object = {};

关于对象的属性名与属性值定义:

代码如下:

var stooge = {
// "first-name" 为属性名,"Jerome" 为属性值
"first-name": "Jerome",
// "last-name" 为属性名,"Howard" 为属性值
"last-name": "Howard"
};

若属性名符合合法标识符规范,则可以省略引号:

代码如下:

var flight = {
airline: "Oceanic",
number: 815,
departure: {
IATA: "SYD",
time: "2004-09-22 14:55",
city: "Sydney"
    },
arrival: {
IATA: "LAX",
time: "2004-09-23 10:42",
city: "Los Angeles"
    }
};

以下示例演示如何访问对象属性:

代码如下:

var owner = { name: "Name5566" };
 
owner.name; // 返回 "Name5566"
owner["name"]; // 返回 "Name5566"
 
owner.job; // 返回 undefined
owner.job = "coder"; // 或者使用 owner["job"] = "coder";

当属性名不符合合法标识符规范时,必须使用引号包裹。如果访问不存在的属性,将返回 undefined。此外,对象通过引用传递而非按值传递:

代码如下:

var x = {};
var owner = x;
owner.name = "Name5566";
x.name; // x.name 等于 "Name5566"

此时,变量 x 和 owner 引用的是同一个对象实例。

可以使用 delete 操作符删除对象的属性:

代码如下:

delete obj.x; // 删除对象 obj 的 x 属性

对象的原型机制

每个对象都关联着一个原型对象(prototype object),从而能够从原型中继承属性。通过对象字面量创建的对象,其原型默认为 Object.prototype 对象(该对象本身没有原型)。在创建对象时,可以指定其原型对象(具体设置方法后续讨论)。当尝试获取(而非修改)对象的属性时,若该对象自身不存在此属性,JavaScript 会沿着原型链向上查找:先检查该对象的原型,若原型中也没有,则继续查找原型的原型,依此类推,直到到达 Object.prototype。与属性获取不同,修改对象自身的属性不会影响原型对象。

函数基础与调用模式

函数基础

在JavaScript中,函数也是对象,它们链接到 Function.prototype 原型对象(而 Function.prototype 又链接到 Object.prototype)。函数拥有一个名为 prototype 的属性,其值为一个对象。该对象包含一个 constructor 属性,其值指向该函数本身:

代码如下:

var f = function() {}
 
typeof f.prototype; // 返回 'object'
typeof f.prototype.constructor; // 返回 'function'
 
f === f.prototype.constructor; // 返回 true

由于函数是对象,可以像操作普通对象一样使用函数:例如将函数赋值给变量、存入数组、作为参数传递,或在函数内部定义其他函数。此外,函数还包含两个隐藏属性:

1. 函数的上下文(this) 2. 函数的代码体

函数的创建方式如下:

代码如下:

var f = function add(a, b) {
return a + b;
}
 
console.log(f); // 输出 [Function: add]

function 关键字后的函数名是可选的。定义函数名主要有两个目的:

1. 支持递归调用 2. 便于调试器和开发工具识别函数

许多情况下并不需要函数名,这种没有名称的函数被称为匿名函数。括号内包裹的是参数列表。JavaScript 不强制要求实参数量与形参数量匹配,例如:

代码如下:

var add = function(a, b) {
return a + b;
}
 
add(1, 2, 3); // 实参数量与形参不匹配

如果提供的实参过多,多余的参数将被忽略;如果实参不足,未赋值的形参值为 undefined。函数必然有返回值,若未通过 return 语句指定,则默认返回 undefined。

一个函数与其所访问的外部变量共同构成闭包(closure)。这是JavaScript的核心特性之一。

函数调用机制

每次函数被调用时,都会接收两个额外的参数:

1. this 2. arguments

this 的值取决于具体的调用模式。JavaScript中存在四种调用模式:

1. 方法调用模式。若对象的属性是函数,则称其为方法。当通过 o.m(args) 形式调用时,this 绑定到对象 o(这意味着在调用发生时,this 才与 o 绑定)。例如:

代码如下:

var obj = {
value: 0,
increment: function(v) {
this.value += (typeof v === 'number' ? v : 1);
    }
};
obj.increment(); // 此时 this 等于 obj

2. 函数调用模式。若函数不是对象的属性,则作为普通函数调用,此时 this 绑定到全局对象。例如:

代码如下:

message = 'Hello World';
var p = function() {
console.log(this.message);
}
 
p(); // 输出 'Hello World'

这种行为有时令人困惑,看以下示例:

代码如下:

obj = {
value: 0,
increment: function() {
var helper = function() {
// 尝试对全局对象中的 value 加 1
this.value += 1;
        }
 
// helper 作为普通函数被调用
// 因此 this 指向全局对象
helper();
    }
};
 
obj.increment(); // obj.value 仍为 0

我们期望的正确结果应如下实现:

代码如下:

obj = {
value: 0,
increment: function() {
var that = this;
var helper = function() {
that.value += 1;
        }
 
helper();
    }
};
 
obj.increment(); // obj.value 等于 1

3. 构造函数调用模式。使用 new 前缀调用的函数称为构造函数,例如:

代码如下:

// Test 被称为构造函数
var Test = function(string) {
this.message = string;
}
 
var myTest = new Test("Hello World");

在函数前加上 new 关键字进行调用(此类函数通常首字母大写)。使用 new 会创建一个链接到该函数 prototype 属性的新对象,且构造函数中的 this 指向该新对象。

4. apply 调用模式。利用函数的 apply 方法调用函数,该方法接收两个参数:第一个指定 this 的值,第二个为参数数组。例如:

代码如下:

var add = function(a, b) {
return a + b;
}
 
var ret = add.apply(null, [3, 4]); // ret 等于 7

在函数调用期间,可以访问一个名为 arguments 的类数组对象(并非真正的 JavaScript 数组),它包含所有传入的实参,从而支持可变长参数:

代码如下:

var add = function() {
var sum = 0;
for (var i=0; i<arguments.length; ++i) {
sum += arguments[i];
    }
return sum;
}
 
add(1, 2, 3, 4);

异常处理与类型扩展

异常处理

接下来介绍JavaScript的异常处理机制。使用 throw 语句抛出异常,通过 try-catch 语句捕获并处理异常:

代码如下:

var add = function (a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
// 抛出异常
throw {
name: 'TypeError',
message: 'add needs numbers'
        };
    }
return a + b;
}
 
// 捕获并处理异常
try {
add("seven");
// e 为抛出的异常对象
} catch (e) {
console.log(e.name + ': ' + e.message);
}

为JavaScript内置类型添加属性

JavaScript中大多数类型都对应一个构造函数:

1. 对象的构造函数为 Object 2. 数组的构造函数为 Array 3. 函数的构造函数为 Function 4. 字符串的构造函数为 String 5. 数字的构造函数为 Number 6. 布尔值的构造函数为 Boolean 7. 正则表达式的构造函数为 RegExp

可以向构造函数的 prototype 添加属性(通常添加方法),使这些属性对相关变量可用:

代码如下:

Number.prototype.integer = function() {
return Math[this < 0 ? 'ceil' : 'floor'](this);
}
 
(1.1).integer(); // 返回 1

作用域与继承机制

作用域机制

JavaScript需要通过函数来构建作用域:

代码如下:

function() {
    // ...
}();

此处创建并执行了一个匿名函数。通过作用域可以隐藏不希望暴露的变量:

代码如下:

var obj = function() {
// 隐藏 value,外部无法直接访问
var value = 0;
 
return {
// 仅有此方法可以修改 value
increment: function() {
value += 1;
        },
// 仅有此方法可以读取 value
getValue: function() {
return value;
        }
    };
}();
 
obj.increment();
obj.getValue() 等于 1;

继承机制

JavaScript实现继承的方式多种多样。在创建对象时,可以设置对象关联的原型对象,具体做法如下:

代码如下:

// 创建一个对象 o,其原型对象为 {x:1, y:2}
var o = Object.create({x:1, y:2});

Object.create 方法定义在 ECMAScript 5 标准中。若使用 ECMAScript 3,可以自行实现一个 create 方法:

代码如下:

// 如果未定义 Object.create 方法
if (typeof Object.create !== 'function') {
// 创建 Object.create 方法
Object.create = function (o) {
var F = function () {};
F.prototype = o;
// 创建一个新对象,此对象的原型对象为 o
return new F();
    };
}

通过 Object.create 方法实现基于原型的继承:新对象直接继承旧对象的属性(与基于类的继承不同,这里无需类的概念,对象直接继承对象)。范例如下:

代码如下:

var myMammal = {
name: 'Herb the Mammal',
get_name: function() {
return this.name;
    },
says: function() {
return this.saying || '';
    }
};
 
// 继承 myMammal
var myCat = Object.create(myMammal);
myCat.name = 'Henrietta';
myCat.saying = 'meow';
myCat.purr = function(n) {
var i, s = '';
for (i = 0; i < n; i += 1) {
if (s) {
s += '-';
        }
s += 'r';
    }
return s;
};
myCat.get_name = function() {
return this.says() + ' ' + this.name + ' ' + this.says();
};

上述代码虽然简单,但无法保护私有成员。可以使用模块模式解决。在模块模式中,某类对象由一个函数生成,并利用函数作用域保护私有成员不被外部访问:

代码如下:

// mammal 函数,用于构造 mammal 对象
var mammal = function(spec) {
// that 为构造的对象
var that = {};
 
// 公有方法 get_name 可被外部访问
that.get_name = function() {
// spec.name 外部无法直接访问
return spec.name;
    };
 
// 公有方法 says 可被外部访问
that.says = function() {
// spec.saying 外部无法直接访问
return spec.saying || '';
    };
 
return that;
};
 
// 创建 mammal 对象
var myMammal = mammal({name: 'Herb'});
 
// cat 函数,用于构造 cat 对象
var cat = function(spec) {
spec.saying = spec.saying || 'meow';
 
// cat 继承自 mammal,因此先构造出 mammal 对象
var that = mammal(spec);
 
// 添加公有方法 purr
that.purr = function(n) {
var i, s = '';
for (i = 0; i < n; i += 1) {
if (s) {
s += '-';
            }
s += 'r';
        }
return s;
    };
 
// 修改公有方法 get_name
that.get_name = function() {
return that.says() + ' ' + spec.name +
' ' + that.says();
return that;
    };
};
 
// 创建 cat 对象
var myCat = cat({name: 'Henrietta'});

在模块模式中,继承是通过调用构造函数实现的。此外,还可以在子类中访问父类的方法:

代码如下:

Object.prototype.superior = function(name) {
var that = this, method = that[name];
return function() {
return method.apply(that, arguments);
    };
};
 
var coolcat = function (spec) {
// 获取子类的 get_name 方法
var that = cat(spec), super_get_name = that.superior('get_name');
that.get_name = function(n) {
return 'like ' + super_get_name() + ' baby';
    };
return that;
};

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章