
“day3 对象与一些基本语法”这个标题一看就是自学编程或者跟着教程做笔记时写的进度记录。我知道不少人看到这种标题会直接划走觉得太基础、太零散但恰恰是这些基础概念藏着一堆日常开发里天天被搜索、被讨论的细节。比如“判断对象为空”“对象数组去重”“对象转querywrapper”这些热搜词哪一个是真正搞清楚了再用的又比如“js中函数是对象吗”“python类和对象”“c类和对象”这些词几乎每个学编程的人都会搜但搜完之后能把概念串起来的人不多。这篇文章就把“对象与基本语法”这件事从头讲透。它适合三类人刚学完循环和条件判断、正要踏入面向对象大门的初学者写了两年代码但对对象模型始终似懂非懂的轮子型选手以及需要快速了解不同语言对象写法的全栈实践者。我不讲那种教科书式的定义而是把对象到底是什么、为什么所有语言都绕不开它、实际写代码时怎么用好它一次说清。1. 对象到底是什么从生活例子到代码模型1.1 对象不是一个“东西”是一组约定我见过太多初学者把对象理解成“一个装着数据的盒子”这话对但远远不够。对象真正的核心是“绑定”它把数据和操作数据的动作绑定在同一个单位里。生活中最贴切的类比是遥控器遥控器上有按键操作内部有电路数据你不需要拆开外壳去改电路你只需要按按键。对象也一样外部只需要调用方法内部状态被封装起来不让外部随便动。用代码来表示这个逻辑Python里最直白class RemoteControl: def __init__(self, brand): self.brand brand self.volume 10 def volume_up(self): if self.volume 100: self.volume 1 rc RemoteControl(some-brand) rc.volume_up() print(rc.volume)这里rc就是一个对象brand、volume是它的数据volume_up是它的行为。外部代码没有直接修改volume而是通过方法去操作这就是封装。理解了这个后面什么多态、继承、组合都是从这个根上长出来的。1.2 类和对象是“图纸”和“房子”的关系很多人把类和对象混着说其实一句话就能分清类是图纸对象是按图纸盖出来的房子。图纸可以反复用每次盖出来的房子可以有不同的装修不同的属性值。一个类可以new出无数个对象每个对象都独立存在互不影响。这点在Python、Java、C里都成立只是在写法上略有差异。理解“类是可复用的模板”这件事非常重要因为很多人的代码写不好就是没有意识到“类”应该描述一类事物的共性而不是某个具体事物。比如你写一个User类里面就不该写死“张三”而应该写“name”和“age”这样的属性概念。具体是谁是创建对象时传入的。1.3 不同语言里对象的“长相”差很多但内核一致热搜词里有一串“python类和对象”“c类和对象”“js中函数是对象吗”“php接口数组对象”这些都是同一个问题在不同语言下的变身。我做个对比大家感受一下语言定义模板的关键字创建对象的方式对象本质Pythonclass类名()一切皆对象类是对象函数也是对象Javaclassnew 类名()对象是类的实例函数不是对象Cclass/struct类名 变量名或new对象是内存中的一段数据加方法JavaScriptclassES6或直接写对象字面量{}或new 类名()函数也是对象对象有原型链PHPclassnew 类名()对象是类的实例数组和对象可以互转看到没有除了JavaScript在“函数也是对象”这件事上比较特殊其他语言的面向对象模型大同小异。你只要吃透任一门的类和对象切换到另一门只是换个关键字的事。2. 对象的创建不同场景下的N种姿势2.1 最基础的newJava与C的经典创建方式创建对象这件事热搜词里专门有“对象的创建”和“nav 第1关对象的创建”说明这是初学者第一个卡点。Java里的标准姿势是new关键字加构造方法public class User { private String name; private int age; public User(String name, int age) { this.name name; this.age age; } } User u new User(张三, 25);C里分两种情况栈上创建直接声明堆上创建用new用完还要手动delete。很多C新手在这里翻车——不是忘了delete就是忘了写析构函数。我的建议是优先使用栈对象除非对象生命周期需要超出函数作用域否则别碰new。2.2 更灵活的构造方式Python的__init__与类方法Python的构造函数其实叫__init__但它并不真正“构造”对象对象的内存分配是__new__做的。绝大多数场景你只需要写__init__。除了常规构造Python还推荐用类方法提供多种创建途径比如class User: def __init__(self, name, age): self.name name self.age age classmethod def from_string(cls, data): name, age data.split(,) return cls(name, int(age)) u User.from_string(张三,25)这种写法在解析配置文件、接口数据时特别有用比在外部写一堆if判断再去User(...)干净得多。你只需要在类内部定义好“从什么格式来”外部只管调用。2.3 JavaScript的对象创建从字面量到classJavaScript里最常用的创建对象方式其实是字面量不需要任何关键字const user { name: 张三, age: 25, sayHi() { console.log(你好我是${this.name}); } };这种方式极其直观但它没有“模板”的概念每个对象都得手写一遍。后来ES6引入了class语法糖让有Java背景的人能快速上手class User { constructor(name, age) { this.name name; this.age age; } sayHi() { console.log(你好我是${this.name}); } } const u new User(张三, 25);那个热搜词“js中函数是对象吗”要单独说一下在JavaScript里函数确实是对象。函数可以拥有属性可以被赋值给变量可以当作参数传递。这是JavaScript最核心的“函数是一等公民”特性跟Java那种“函数不是对象”的模型完全不同。理解这个你才能真正理解为什么回调、闭包、高阶函数在JS里无处不在。3. 判断对象是否为空不同语言里的“坑”合集3.1 Java的Optional与判空“Optional对象操作”和“判断对象为空”是Java日常开发里搜烂了的问题。最原始的写法是if (obj ! null)但嵌套多了以后代码又臭又长于是Java 8引入了OptionalOptionalUser optionalUser Optional.ofNullable(user); optionalUser.ifPresent(u - System.out.println(u.getName())); String name optionalUser.map(User::getName).orElse(未知);使用Optional的核心心法是不要用它去判断“某个变量是不是null”而是用它去表达“某个值可能存在也可能不存在”。这两者看起来差不多但用起来整个代码的思维方式是不同的。Optional最忌讳的就是你拿到一个Optional对象之后又去调get()那跟直接判空再取值没有本质区别反而多了个NoSuchElementException的风险。3.2 JavaScript里空对象判断为什么这么麻烦两个热搜词“判断是不是空对象 js”和“判断对象为空”都指向同一个痛点JavaScript里obj null只能判断null和undefined根本不能判断对象里有没有内容。而{}是一个真实存在的对象只是没有属性。怎么判断一个对象是不是“空对象”最常用的是Object.keysconst isEmpty (obj) Object.keys(obj).length 0;但这里有个大坑如果对象本身是null或undefined直接调Object.keys会报错所以要先判空再判断。安全的写法是const isEmpty (obj) { if (obj null) return true; return Object.keys(obj).length 0; };还有个更深层的坑如果你用new Date()、正则表达式或者一个数组来调用这个函数Object.keys返回的数组长度可能是0但这个对象显然不是“空对象”。所以判断空对象一定要先确认对象类型否则就属于“类型判断没做判空跟着出错”。3.3 Python施行的“空”判断Python的判空是最舒服的直接利用布尔上下文if not my_list: print(列表是空的) if not my_dict: print(字典是空的) if obj is None: print(对象是None)这里要注意区分“空容器”和“None”——[]、{}、在布尔上下文中都是False但None也是False。有时候业务上的“空对象”指的是“属性值全为空”那就得写一个专门的方法来判断不能简单用if not obj。比如一个用户对象它有name和age如果两个字段都是空业务上才算“空用户”。这种判断必须显式写清楚字段不能指望语言给你一个万能方法。4. 对象的高频操作从拷贝到去重全是生产环境刚需4.1 对象数组去重不只是Set一下就完事热搜词“对象数组去重”是个非常经典的场景。很多人一上来就说用Set基础类型数组可以对象数组不行——因为每个对象的内存地址不同即使内容完全一样Set也会认为是两个不同的对象。对象数组去重必须指定“去重依据”。JavaScript的常用解法是用Mapconst arr [ { id: 1, name: 张三 }, { id: 2, name: 李四 }, { id: 1, name: 张三 } ]; const uniqueArr [...new Map(arr.map(item [item.id, item])).values()];这个写法的思路分三步先用map把数组转换成“id作为键对象作为值”的二维数组然后用Map的特性自动去重后面的覆盖前面的最后用展开运算符把值取出来。如果你想保留第一次出现的数据而不是覆盖那就需要反过来判断如果Map里还没有这个id才放进去。4.2 对象深度拷贝万不可直接赋值“java对象深度拷贝”背后的本质是直接赋值只拷贝了引用原对象和新变量指向同一个内存地址改一个另一个跟着变。Java里实现深拷贝常见方式有几种重写clone()方法但默认是浅拷贝使用序列化方式对象流但要求类实现Serializable使用三方库如Gson、Jackson等先转JSON再转回对象。我自己在项目中用得最多的是Gson方案Gson gson new Gson(); User copy gson.fromJson(gson.toJson(originalUser), User.class);这个方案缺点是性能一般如果对象里有Date、BigDecimal这类特殊类型转换时可能丢失格式。所以更稳妥的做法是明确要求所有需要深拷贝的类都实现自定义的拷贝构造方法或copy()方法自己控制每个字段的复制逻辑。别偷懒深拷贝这种操作交给语言“自动”做反而最容易出事。JavaScript的深拷贝也曾是难题过去得靠JSON.parse(JSON.stringify(obj))或递归手写。现在structuredClone已经普及是浏览器原生支持的深拷贝方案支持循环引用和Date、Map、Set这些特殊类型性能也更好const copy structuredClone(originalObj);注意这个函数在旧浏览器和某些运行环境里可能不存在生产环境如果要兼容仍需要做能力判断。4.3 ES6提取对象数组的一部分解构与数组方法结合“es6提取数组对象一部分”这个搜索词其实就是“从对象数组里取出几个字段组成新数组”。假如你有一个用户数组里面有id、name、age、address、phone你只想要id和name优雅的ES6写法是const users [ { id: 1, name: 张三, age: 25, address: 北京 }, { id: 2, name: 李四, age: 30, address: 上海 } ]; const simplified users.map(({ id, name }) ({ id, name }));这里的解构语法({ id, name })从每个对象中取出两个字段箭头函数右侧的({ id, name })再组装成新对象。这个操作很常用写起来也很有“声明式”的感觉——不是告诉机器“怎么取”而是告诉机器“要什么”。4.4 Optional对象操作链式调用大幅减少if嵌套接着前面的Optional说实际开发中Optional最爽的场景是链式操作。比如取用户的省份名称以前写String provinceName ; if (user ! null) { Address addr user.getAddress(); if (addr ! null) { Province province addr.getProvince(); if (province ! null) { provinceName province.getName(); } } }用了Optional之后String provinceName Optional.ofNullable(user) .map(User::getAddress) .map(Address::getProvince) .map(Province::getName) .orElse();每一层如果为空链式调用会自动短路最后返回orElse里的默认值。这种写法不仅代码短更重要的是逻辑顺——你从上往下读就是在依次“安全地取每一层”而不是在维护三层的if嵌套。4.5 映射对象类型不要把“类型转换”和“数据转换”搞混“映射对象类型”这个热搜词在不同语境下含义不同。Python里常见的是用dict做映射比如把数据库查出来的元组转成字典Java里常见的是DTO、VO、Entity之间的转换JavaScript里的映射对象更多是指像{ A: 1, B: 2 }这种键值映射结构用来替代大量的if-else。我特别想提醒的是对象映射的时候要先分清“类型”和“数据”。类型是编译时的概念数据是运行时的概念。比如Java里把一个User对象映射成UserVO对象你可以手写一个个setter也可以用BeanUtils.copyProperties。但BeanUtils是浅拷贝如果你在原对象里改了某个嵌套对象的内容映射出来的VO也会跟着变。这个坑一旦踩上排查起来很痛苦因为表面上是两个不同的对象内存里却共用着某个嵌套引用。5. 那些被反反复复搜索的对象问题快查5.1 从jquery到vue前端对象的获取与绑定“jquery根据name获取对象”是老一辈前端天天写的代码$(input[nameusername])它的本质是利用属性选择器在现代原生JavaScript里等价于document.querySelector(input[nameusername])“uni-app vue3 ref万能对象”则完全是另一种思路——在Vue 3里ref可以把任意值包括对象、数组变成响应式引用。操作的时候要记得用.value访问import { ref } from vue; const user ref({ name: 张三 }); user.value.name 李四;很多人写Vue 3踩坑就是忘了.value在模板里用user.name在脚本里又用user.value.name两头不一致的时候就会出错。记住一条规则在脚本里操作ref包裹的对象必须先.value在模板里Vue会自动解包不需要也不能写.value。5.2 django与数据库对象操作“django执行查询-删除对象”是Django ORM的新手必修课。查出来一个对象之后想删有三道工序要先检查这个对象是不是真正存在于数据库、有没有外键关联别的记录、删除时需不需要级联删除。user User.objects.get(id1) user.delete()这是最标准的删除操作。但如果User被其他表用外键关联直接删会触发数据库约束错误Django会抛出ProtectedError。解决方案是在外键字段上设置on_deletemodels.CASCADE或者SET_NULL具体用哪种看业务需求关联数据没意义就级联删关联数据还要保留就置空。5.3 对象存储服务此“对象”非彼“对象”热搜词里的“oss对象存储”“对象存储服务”跟编程里的“对象”完全是两码事——它指的是云存储里的一种数据存储模式。对象存储把文件作为“对象”来管理每个对象包含数据本身、元数据和唯一标识符。这种方式和传统的文件系统存储、块存储有本质区别存储类型数据组织方式典型场景文件存储目录层级本地磁盘、NFS共享块存储数据块数据库的磁盘对象存储扁平命名空间元数据图片、视频、备份文件理解了这种区别你就能明白为什么很多现代应用把用户上传的头像、文件都放到对象存储里而不是存到服务器本地——对象存储天生适合海量非结构化数据扩容方便还能通过CDN加速分发。5.4 底层相关的对象cpp结构体链表与qt元对象系统“c结构体链表基本语法”是数据结构课的基础。结构体struct是C里最简单也最原始的对象雏形——它只有数据没有方法。链表节点就是典型的例子struct Node { int data; Node* next; };一个节点持有数据和指向下一个节点的指针环环相扣就形成了链表。理解这个你对“对象是一块内存”这句话会有非常具象的认识。“qt元对象系统”则是C一个特立独行的扩展。C本身没有反射能力但Qt通过Q_OBJECT宏加上元对象编译器让类可以在运行时查询自己的属性、信号和槽。这是C世界里少见的“半反射”机制理解它需要先理解QObject基类和QMetaObject。5.5 ansys workbench的“对象报错”与编程对象无关那个“ansys workbench模态求解报错 求解过程中出现未知错误”的热搜词虽然不是编程问题但从排查思路看跟对象问题很有共通之处。软件里的“对象”指的往往是求解对象、几何对象、网格对象。报错里说“检查求解信息对象上的求”意思是让你去模型树里选中出问题的求解对象右键查看详细信息。这种问题八成出在边界条件设置不对、网格质量太差或者求解器内存不够。排错的核心思路和代码一样先定位对象再看对象的状态和属性最后修正。6. 踩坑实录汇总对象操作里最容易翻车的三个细节6.1 浅拷贝和深拷贝搞混改一处动全局这个坑我在第4节已经讲过但还要再强调一遍对象赋值、对象传递、对象拷贝是三件完全不同的事。赋值只是让两个变量指向同一块内存传递是把这个引用给到函数里但函数外面还是那个对象只有拷贝才会真正产生一块新内存。很多“改了A发现B也变了”的问题追根溯源都是浅拷贝和深拷贝没分清。有个判断口诀直接是引用clone要看实现默认是浅的序列化、JSON转换、手写逐字段复制才是深拷贝。不管用哪种语言先判断业务需求是“共享状态”还是“独立副本”再决定用哪种方式。6.2 判空顺序不对空对象依然炸判空也要讲究顺序。最常见的错误是先访问对象的属性然后才想起来判断对象是否为空。正确做法永远是先判断对象本身是否为null/undefined再判断对象里的字段或内容是否为空。尤其JavaScript里null和undefined是两个不同的“空”0和又都是“非空”每一步都要想清楚。Python里if not obj看起来很好用但当obj是numpy数组时会直接报错因为数组的布尔值是不确定的。所以在科学计算场景里判断“数组非空”应该用obj.size 0而不是if obj。6.3 对象的可变性没搞清函数参数被悄悄修改Python里把对象传入函数函数里改了对象的属性外面那个对象也跟着变了。这是“可变对象传引用”的效果。很多初学者以为传入函数就“复制了一份”其实没有。如果你不希望外部对象被改必须在函数内部先做一份拷贝。def update_user(user): user.name 李四 # 外部对象也会变 def update_user_safe(user): user copy.deepcopy(user) user.name 李四 # 只改副本Java里也一样对象传入方法后方法内部通过引用修改了对象的字段外部对象随之改变。这是语言设计如此不是bug。要避免就学函数式编程的思路不修改输入而是创建一个新对象返回。7. 对象这套东西到底该怎么学才牢靠学了这么多语言里的对象我想把最重要的是最后一点经验分享出来。很多人学对象喜欢背语法今天学Python的类明天学Java的类后天学C的类每个都学了个皮毛。我的建议是选定一门主语言把它彻底吃透尤其是“对象”这个概念在不同场景下的形态——普通的业务对象、数据传输对象、值对象、实体对象、服务对象它们各自的职责是什么生命周期怎么管理需要满足哪些约束。这门语言的对象模型你真的搞清楚了其他语言都是换皮不换骨。还有个很有效的做法是“对比学习”。同一个需求分别用Python、JavaScript、Java各写一遍比如“创建用户对象并判断它是否为空”做一次对比你对每种语言的差异和共同点都会记得非常牢。对象不是背出来的是用出来的。你今天写的每一个类都是在用自己的方式重新理解“封装”“状态”“行为”“对象之间的关系”这些永恒的主题。最后分享一个我自己的习惯无论在哪门语言里写对象我都会问自己三个问题——这个对象怎么创建创建之后怎么验证它是对的它和其他对象怎么交互这三个问题想清楚了代码基本不会差到哪里去。对象的话题很大但大的话题都是从一个又一个具体的小问题上长出来的你今天把这些细节都理顺了之后看到任何对象相关的代码都不会再发怵。