【前端(十二)】JavaScript 函数与对象笔记

文章目录

  • [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-elseforwhile 等)与 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);
    }
};
  • nameage 是属性,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 原型与原型链,这里只给结论):

  1. 创建一个空对象;
  2. 将该对象的原型([[Prototype]])链接到构造函数的 prototype 对象上;
  3. 将函数内的 this 绑定到这个新对象;
  4. 执行构造函数体,为新对象添加属性;
  5. 返回这个新对象(如果构造函数没有手动返回其他对象)。

⚠️ 忘记 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.keyobj[key] 的区别

javascript 复制代码
let key = "age";
console.log(person[key]); // 正确:会先求出变量 key 的值为 "age",再取 person.age
console.log(person.key);  // 错误:等价于 person["key"],寻找名为 "key" 的属性(不存在!)

方括号内需要放入字符串 (或求值结果为字符串的表达式)。因为 JavaScript 对象的属性名在底层默认就是字符串,person[变量] 会先算出变量存储的字符串,再用该字符串去查找属性。

增、删、改、查
操作 语法 说明
读(查询) obj.propobj["prop"] 自身没有时,会沿原型链向上查找
改(赋值) obj.prop = 新值 属性存在则更新,不存在则在自身新增
增(新增) 同上 直接给不存在的属性赋值即可新增
delete obj.propdelete 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,上面挂着 toStringhasOwnProperty 等公共方法。
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("咪咪");

引擎内部自动执行:

  1. 创建一个全新的空对象 {}
  2. 将新对象的 [[Prototype]] 指向 Animal.prototype(这是原型链的核心连接点);
  3. 以这个新对象为 this,执行 Animal 函数体,为它添加 name 等属性;
  4. 如果构造函数没有显式返回另一个对象,则自动返回该新对象,赋值给 cat

结果:

javascript 复制代码
Object.getPrototypeOf(cat) === Animal.prototype; // true

一句话记住二者的关系:
构造函数.prototypenew 的瞬间成为新实例的 [[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(); // 旺财 在吃东西
  • catdog 自身都没有 eat,但它们的原型都是 Animal.prototype,所以能找到。
  • 调用 cat.eat() 时,this 指向 cat;调用 dog.eat() 时,this 指向 dogthis 的指向由调用对象决定,与从哪个原型上找到的方法无关。
  • 所有实例共享 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
  • 实例查找属性:catAnimal.prototypeObject.prototypenull
  • 函数查找属性:AnimalFunction.prototypeObject.prototypenull
8. 两个极易混淆的概念对比
概念 谁有? 是什么? 何时起作用?
[[Prototype]] 每个对象 指向原型对象的内部链接(隐藏属性) 每次访问属性,自身没有时,就沿着它向上查找
prototype 每个函数 函数身上的一个普通对象,默认有 constructor 当函数被 new 调用时,成为新实例的 [[Prototype]]

总结一句话:

属性查找顺藤摸瓜([[Prototype]]),函数的 prototype 就是 new 时拴住这根藤的起点。


以上为个人学习总结,旨在梳理个人理解。如有疏漏或不当之处,欢迎指正与交流。

相关推荐
你真的快乐吗2 小时前
@fuxishi/svg-icon:一个 Vue 3 svg本地图标+iconify图标组件库,让图标管理不再头疼
前端·vue.js·typescript
Rkgua2 小时前
ESModule和Commonjs模块的区别
前端·javascript
江南十四行2 小时前
ReAct Agent 基本理论与项目实战(二)
前端·react.js·前端框架
用户600071819102 小时前
【翻译】React 如何乱序流式输出 UI,却仍保持最终顺序
前端
江南十四行2 小时前
AI Agent应用类型及Function Calling开发实战(三)
服务器·前端·javascript
yqcoder2 小时前
JavaScript 数据类型全景图:从基础到进阶
开发语言·javascript·ecmascript
GISer_Jing2 小时前
AI原生全栈架构理论体系:从分布式范式演进到全链路工程化理论基石
前端·人工智能·学习·ai编程
GISer_Jing2 小时前
从“切图仔”到“增长架构师”:AI时代营销前端的范式革命
前端·人工智能·ai编程
三块可乐两块冰2 小时前
机器学习三十八
笔记