文章目录
- [JavaScript 函数与对象笔记](#JavaScript 函数与对象笔记)
-
- [1 函数](#1 函数)
-
- [1.1 定义函数的两种方式](#1.1 定义函数的两种方式)
- [1.2 调用函数时不检查参数个数](#1.2 调用函数时不检查参数个数)
- [1.3 常用内置函数](#1.3 常用内置函数)
- [2 对象](#2 对象)
-
- [2.1 创建对象的三种方式](#2.1 创建对象的三种方式)
-
- [① 字面量 {} ------ 最常用、最直接](#① 字面量 {} —— 最常用、最直接)
- [② 构造函数 + new ------ 批量生产结构相同的对象](#② 构造函数 + new —— 批量生产结构相同的对象)
- [③ Object.create() ------ 备用招式,了解即可](#③ Object.create() —— 备用招式,了解即可)
- [2.2 属性访问与操作](#2.2 属性访问与操作)
- [2.3 原型与原型链](#2.3 原型与原型链)
-
- [1. 现象:我没定义的属性从哪来的?](#1. 现象:我没定义的属性从哪来的?)
- [2. 原型是什么?](#2. 原型是什么?)
- [3. 手动设置原型](#3. 手动设置原型)
- [4. 构造函数与 prototype 属性](#4. 构造函数与 prototype 属性)
- [5. new 做了什么?(详细拆解)](#5. new 做了什么?(详细拆解))
- [6. 方法共享:放在 prototype 上](#6. 方法共享:放在 prototype 上)
- [7. 终极图解](#7. 终极图解)
- [8. 两个极易混淆的概念对比](#8. 两个极易混淆的概念对比)
JavaScript 函数与对象笔记
JavaScript 的流程控制(
if-else、for、while等)与 Java 完全一致,详情见此。本文只聚焦 函数 与 对象 两大块。
1 函数
函数是执行特定任务的代码块,作用与 Java 中的方法相同,但定义和使用都更加灵活。
1.1 定义函数的两种方式
JavaScript 属于动态类型语言,变量类型由运行时值决定,因此形式参数和返回值都不需要声明类型。
方式一:函数声明
javascript
function 函数名(参数1, 参数2, ...) {
// 要执行的代码
return 返回值; // 可选,无 return 则返回 undefined
}
方式二:函数表达式
javascript
var 函数名 = function (参数1, 参数2, ...) {
// 要执行的代码
};
这两种方式创建的多数情况下可以互换,主要区别在于:函数声明会被提升(可以在声明前调用),而函数表达式只有在赋值后才可以调用。
1.2 调用函数时不检查参数个数
JavaScript 函数的调用非常宽容:只要函数名正确,传入任意数量、任意类型的参数都可以执行,不会报错。
示例
javascript
function add(a, b) {
return a + b;
}
// 调用时多传参数
console.log(add(10, 20, 30, 40)); // 输出 30
在上面的调用中,10 传给 a,20 传给 b,多余的 30 和 40 没有对应的形参接收,但不会影响函数正常执行 。如果少传参数,未接收值的形参会自动赋值为 undefined。
1.3 常用内置函数
| 函数 | 说明 | 示例 |
|---|---|---|
console.log() |
在控制台打印信息,调试神器 | console.log('你好') |
alert() |
弹出警告提示框 | alert('操作成功!') |
prompt() |
弹出输入框,返回用户输入的内容(字符串) | let name = prompt('请输入姓名') |
confirm() |
弹出确认框,返回 true / false |
if(confirm('确定删除?')){...} |
typeof |
返回一个值的数据类型字符串 | typeof 123 → "number" |
isNaN() |
判断值是否为 NaN(非数字)。若想避免类型转换引起的误判,可用 Number.isNaN() |
isNaN("abc") → true |
isFinite() |
判断值是否为有限数字(既不是 NaN,也不是 Infinity 或 -Infinity) |
isFinite(1/0) → false |
parseInt() |
将字符串转换为整数。从左边第一个字符开始逐个解析,直到遇到不能转为数字的字符为止;若第一个字符就不能转,则返回 NaN。可传入第二个参数指定进制,如 parseInt("11", 2) → 3 |
parseInt("3.14") → 3 |
parseFloat() |
将字符串转换为浮点数。解析规则与 parseInt 类似,但可识别小数点且只解析十进制 |
parseFloat("3.14abc") → 3.14 |
encodeURIComponent() |
将字符串编码为安全的 URI 组件 | encodeURIComponent("你好") → "%E4%BD%A0%E5%A5%BD" |
decodeURIComponent() |
解码由 encodeURIComponent() 生成的字符串 |
decodeURIComponent("%E4%BD%A0") → "你" |
eval() |
将字符串作为 JavaScript 代码执行。安全风险极高,应尽量避免使用 | eval("1+2") → 3 |
2 对象
在 Java 中,需要先定义 class,再 new ClassName() 来创建对象。JavaScript 不同,没有类(ES6 的 class 只是语法糖),对象是核心,创建方式也灵活得多。
2.1 创建对象的三种方式
① 字面量 {} ------ 最常用、最直接
javascript
let person = {
name: "张三",
age: 20,
sayHello: function() {
console.log("你好,我是" + this.name);
}
};
name、age是属性,sayHello是方法。- 属性的值可以是任何类型:数字、字符串、布尔、数组、函数,甚至另一个对象。
- 对象的键名默认是字符串(即便写
{1: "a"},键名也会自动转为字符串"1")。ES6 开始还可以使用Symbol作为键名。
适用场景:只需创建一两个孤立对象时,字面量最快、最清晰。
② 构造函数 + new ------ 批量生产结构相同的对象
需要重复创建多个相同结构的对象(比如十个学生)时,用构造函数。
javascript
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHello = function() {
console.log("你好,我是" + this.name);
};
}
let p1 = new Person("张三", 20);
let p2 = new Person("李四", 25);
约定 :构造函数名首字母大写(提示调用者必须用 new)。
new 关键字的底层流程
(详细拆解见 2.3 原型与原型链,这里只给结论):
- 创建一个空对象;
- 将该对象的原型(
[[Prototype]])链接到构造函数的prototype对象上; - 将函数内的
this绑定到这个新对象; - 执行构造函数体,为新对象添加属性;
- 返回这个新对象(如果构造函数没有手动返回其他对象)。
⚠️ 忘记
new的后果如果直接调用
Person("王五", 30),函数内的this会指向全局对象(浏览器中是window,严格模式下为undefined),导致属性泄漏到全局作用域,出现难以追踪的 bug。构造函数必须用new调用。
③ Object.create() ------ 备用招式,了解即可
javascript
let obj = Object.create(proto);
创建一个新对象,并将其原型直接指定为 proto,跳过构造函数这一步。常用于底层库对原型的精细控制,日常业务中较少使用,新手不必深究。
2.2 属性访问与操作
点号 . vs 方括号 []
| 方式 | 语法 | 何时能用 | 特点 |
|---|---|---|---|
| 点号 | obj.prop |
属性名是合法的标识符(由字母、数字、_、$ 组成,且不以数字开头) |
简洁直观 |
| 方括号 | obj["prop"] 或 obj[变量] |
任何情况(属性名包含特殊字符、数字开头,或者属性名保存在变量中时) | 万能,支持动态计算 |
核心坑点:obj.key 和 obj[key] 的区别
javascript
let key = "age";
console.log(person[key]); // 正确:会先求出变量 key 的值为 "age",再取 person.age
console.log(person.key); // 错误:等价于 person["key"],寻找名为 "key" 的属性(不存在!)
方括号内需要放入字符串 (或求值结果为字符串的表达式)。因为 JavaScript 对象的属性名在底层默认就是字符串,person[变量] 会先算出变量存储的字符串,再用该字符串去查找属性。
增、删、改、查
| 操作 | 语法 | 说明 |
|---|---|---|
| 读(查询) | obj.prop 或 obj["prop"] |
自身没有时,会沿原型链向上查找 |
| 改(赋值) | obj.prop = 新值 |
属性存在则更新,不存在则在自身新增 |
| 增(新增) | 同上 | 直接给不存在的属性赋值即可新增 |
| 删 | delete obj.prop 或 delete obj["prop"] |
仅删除对象自身的属性,原型上的无法删除 |
| 检查存在性 | "prop" in obj |
检查整条原型链 |
| 检查自有属性 | obj.hasOwnProperty("prop") |
只检查自身,不查原型 |
遍历属性
for...in 循环
javascript
for (let key in person) {
console.log(key, person[key]); // key 是字符串,必须用 [] 取值
}
这里的 key 是遍历到的属性名字符串,所以只能用 person[key] 而不能写 person.key (后者等价于 person["key"],与循环变量无关)。
for...in 会遍历对象自身 以及原型链上所有可枚举 的属性。为避免原型方法干扰,通常用 hasOwnProperty 过滤:
javascript
for (let key in person) {
if (person.hasOwnProperty(key)) {
console.log(key, person[key]);
}
}
方法也是属性
JavaScript 中函数可以像普通值一样赋给属性。因此,方法仅仅是"值为函数的属性"而已。person.sayHello 拿到的是函数对象,加上 () 才会调用;person["sayHello"]() 完全合法。
2.3 原型与原型链
1. 现象:我没定义的属性从哪来的?
javascript
let obj = { name: "张三" };
console.log(obj.name); // "张三" ------ 自己的属性
console.log(obj.age); // undefined ------ 没有,合理
console.log(obj.toString); // function ------ 我根本没定义 toString!
答案是:每个对象都有一个"隐藏的后备对象",称为 原型。自己找不到属性时,引擎会去原型上找。
2. 原型是什么?
- 每个对象都有一个内部隐式链接
[[Prototype]],指向它的原型对象。 - 通过
Object.getPrototypeOf(obj)可以查看该链接指向谁(不支持直接访问[[Prototype]])。 - 普通对象默认的原型是
Object.prototype,上面挂着toString、hasOwnProperty等公共方法。
text
obj → Object.prototype → null
- 属性查找规则:先搜自身 → 没有就沿
[[Prototype]]链向上 → 直到找到或到达null(原型链终点)。
3. 手动设置原型
除了默认的 Object.prototype,也可以自己指定原型:
javascript
let parent = { company: "Google" };
let child = { name: "小明" };
Object.setPrototypeOf(child, parent);
console.log(child.name); // "小明" ------ 自己的
console.log(child.company); // "Google" ------ 沿着原型在 parent 上找到
此时原型链:
text
child → parent → Object.prototype → null
4. 构造函数与 prototype 属性
每当创建一个函数,JS 引擎会自动为它配发一个 prototype 属性。这个属性是一个普通对象,默认只包含一个 constructor 属性,指向函数自身。
javascript
function Animal(name) {
this.name = name;
}
console.log(Animal.prototype); // { constructor: Animal }
[[Prototype]]:所有对象都有的隐式原型链链接。prototype:只有函数 才有的属性,专门为new服务。
5. new 做了什么?(详细拆解)
当我们用 new 调用构造函数时:
javascript
let cat = new Animal("咪咪");
引擎内部自动执行:
- 创建一个全新的空对象
{}; - 将新对象的
[[Prototype]]指向Animal.prototype(这是原型链的核心连接点); - 以这个新对象为
this,执行Animal函数体,为它添加name等属性; - 如果构造函数没有显式返回另一个对象,则自动返回该新对象,赋值给
cat。
结果:
javascript
Object.getPrototypeOf(cat) === Animal.prototype; // true
一句话记住二者的关系:
构造函数.prototype 在 new 的瞬间成为新实例的 [[Prototype]]。实例通过 [[Prototype]] 链找到 prototype 上共享的方法。
6. 方法共享:放在 prototype 上
javascript
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(this.name + " 在吃东西");
};
let cat = new Animal("咪咪");
let dog = new Animal("旺财");
cat.eat(); // 咪咪 在吃东西
dog.eat(); // 旺财 在吃东西
cat和dog自身都没有eat,但它们的原型都是Animal.prototype,所以能找到。- 调用
cat.eat()时,this指向cat;调用dog.eat()时,this指向dog。this的指向由调用对象决定,与从哪个原型上找到的方法无关。 - 所有实例共享
Animal.prototype上同一个eat函数,不会为每个实例单独创建副本。
javascript
console.log(cat.eat === dog.eat); // true
7. 终极图解
text
cat (实例对象)
· name: "咪咪"
· [[Prototype]] ────→ Animal.prototype
· eat: function
· constructor: Animal
· [[Prototype]] ────→ Object.prototype
· toString: function
· hasOwnProperty: function
· [[Prototype]]: null
Animal (函数本身也是对象)
· prototype: 指向 Animal.prototype
· [[Prototype]] ────→ Function.prototype
· call / apply / bind
· [[Prototype]] ────→ Object.prototype → null
- 实例查找属性:
cat→Animal.prototype→Object.prototype→null - 函数查找属性:
Animal→Function.prototype→Object.prototype→null
8. 两个极易混淆的概念对比
| 概念 | 谁有? | 是什么? | 何时起作用? |
|---|---|---|---|
[[Prototype]] |
每个对象 | 指向原型对象的内部链接(隐藏属性) | 每次访问属性,自身没有时,就沿着它向上查找 |
prototype |
每个函数 | 函数身上的一个普通对象,默认有 constructor |
当函数被 new 调用时,成为新实例的 [[Prototype]] |
总结一句话:
属性查找顺藤摸瓜([[Prototype]]),函数的 prototype 就是 new 时拴住这根藤的起点。
以上为个人学习总结,旨在梳理个人理解。如有疏漏或不当之处,欢迎指正与交流。