前言
本文是JavaScript基础系列的第六篇,深入讲解对象的方方面面。内容包括对象的四种创建方式(new构造函数、对象字面量、工厂函数、构造函数)、属性枚举、this指向规则、原型对象与原型链、垃圾回收机制以及ES6的class语法。掌握这些内容,你将真正理解JavaScript面向对象编程的精髓。
一、对象的创建方式(四种)
1.1 new构造函数创建对象
var obj = new Object(); obj.name = "jack"; obj.age = 18;特点:使用new关键字调用的函数称为构造函数,专门用来创建对象。
1.2 对象字面量创建对象
使用花括号{}包含属性和方法,适合在定义对象时同时设置属性值。
let obj2 = { // 完整写法 name: "tom", age: 18, gender: "男", // 属性写法二:属性名简写(ES6) // name, // 等同于 name: name // 方法写法一:完整写法 sayHi: function() { console.log("hi"); }, // 方法写法二:匿名函数提出去 // sayHi: fun, // 方法写法三:简写方式(ES6) sayHi() { console.log("hi"); } };注意:
属性与方法采用键值对形式:
属性名: 属性值多个属性或方法中间用逗号隔开
最后一个属性或方法后面的逗号可以省略
方法冒号后跟的是一个匿名函数
1.3 读取对象
// 调用对象的属性 obj2.name; // 对象名.属性名 obj2['age']; // 对象名['属性名'] // 调用对象的方法 obj2.sayHi(); // 对象名.方法名()1.4 对象方法
定义:函数作为对象的属性保存时,称为这个对象的方法(method)。
let obj = {}; obj.sayHi = function() { console.log("hi"); }; obj.sayHi(); // 调用方法注意:方法与函数本质上没有区别,只是名称上的区分——方法依托于对象存在。
二、对象属性枚举:for...in语句
var obj = { name: "孙悟空", age: 18, gender: "男", address: "花果山" }; for (let key in obj) { console.log(key); // 输出属性名 console.log(obj[key]); // 输出属性值 }特点:
对象中有几个属性,循环体就执行几次
每次执行时将对象中的一个属性名赋值给变量
key通过
obj[key]获取对应的属性值
三、变量与属性、函数与方法的区别
对比项 | 变量 vs 属性 | 函数 vs 方法 |
|---|---|---|
存储内容 | 都是用来存储数据 | 都是用来实现功能 |
声明方式 | 变量单独声明;属性在对象中无需声明 | 函数单独存在;方法是对象的属性 |
使用方式 | 变量直接写变量名;属性通过 | 函数直接调用;方法通过 |
四、this关键字
4.1 this的本质
解析器在调用函数时,每次都会向函数内部传递一个隐含的参数——this。this指向的是一个对象,称为函数执行的上下文对象。
4.2 this的指向规则
function fun() { console.log(this.name); } var obj = { name: "孙悟空", sayName: fun }; var obj2 = { name: "沙和尚", sayName: fun }; fun(); // 1. 函数形式调用:this指向window obj.sayName(); // 2. 方法形式调用:this指向调用方法的对象(obj) obj2.sayName(); // 3. this指向obj2this指向总结:
调用方式 | this指向 |
|---|---|
函数形式调用 | window |
方法形式调用 | 调用方法的那个对象 |
构造函数形式调用 | 新创建的那个对象 |
五、工厂函数创建对象
使用工厂方法可以大批量地创建对象。
function createObj(name, age) { let obj1 = { name: name, age: age, study() { console.log("学习"); } }; return obj1; } let p1 = createObj("jack", 18); let p2 = createObj("tom", 19);问题:创建的对象都是Object类型,无法区分不同类型的对象(比如无法区分"人"和"狗")。
六、构造函数
6.1 构造函数定义
构造函数是一种特殊的函数,主要用来初始化对象,为对象成员变量赋值初始值,通常与new运算符一起使用。
function Person(name, age) { this.name = name; this.age = age; this.sayHi = function() { console.log("hi"); }; } let per1 = new Person("jack", 18); let per2 = new Person("tom", 18);6.2 构造函数规范
规范 | 说明 |
|---|---|
函数名首字母大写 | 便于区分普通函数 |
必须使用 | 否则会污染全局作用域 |
使用 | this指向新创建的对象 |
6.3 构造函数执行流程
立即创建一个新对象
将新对象设置为函数中的this
逐行执行函数中的代码
将新对象作为返回值返回
6.4 对象的实例化
类:使用同一个构造函数创建的一类对象(如"人类")
实例:通过构造函数创建的具体对象(如"刘德华")
实例化:利用构造函数创建对象的过程
6.5 instanceof运算符
检查一个对象是否是一个类的实例。
console.log(per1 instanceof Person); // true console.log(per1 instanceof Object); // true(所有对象都是Object的后代)七、构造函数优化
7.1 存在的问题
每次创建对象时,方法都会被重新创建一次,造成内存浪费。
function Person(name, age) { this.name = name; this.age = age; this.sayNam = function() { // 每次创建都会生成一个新函数 alert("hello大家好,我是" + this.name); }; }7.2 优化方案:将方法提取到全局
function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; this.sayNam = fun; // 所有实例共享同一个方法 } // 将方法定义在全局作用域中 function fun() { alert("hello大家好,我是" + this.name); } var per = new Person("孙悟空", 18, "男"); var per2 = new Person("猪八戒", 28, "男"); per.sayNam(); // 孙悟空 per2.sayNam(); // 猪八戒 console.log(per.sayNam === per2.sayNam); // true问题:将函数定义在全局作用域会污染全局命名空间,也不安全。
更好的解决方案:使用原型对象。
八、原型对象(prototype)
8.1 原型对象的定义
每一个函数都有一个属性
prototype,指向一个对象——原型对象默认情况下,原型对象是一个空对象
当函数作为构造函数调用时,所创建的对象都会有一个隐含属性
__proto__,指向该构造函数的原型对象
function MyClass() {} // 向原型对象添加属性和方法 MyClass.prototype.a = 1; MyClass.prototype.study = function() { console.log("study"); }; let mc = new MyClass(); console.log(mc.__proto__ === MyClass.prototype); // true8.2 原型对象的作用
作用 | 说明 |
|---|---|
公共区域 | 所有同类实例都可访问 |
避免污染全局作用域 | 属性和方法定义在原型上,不在全局 |
实现属性和方法共享 | 所有实例共享原型上的属性和方法 |
8.3 属性检测
// in运算符:会沿着原型链查找 console.log('a' in p1); // true(原型上有也算) // hasOwnProperty:只检测当前对象自身 console.log(p1.hasOwnProperty('a')); // false(原型上的不算自身)九、垃圾回收(GC)
9.1 什么是垃圾
当一个对象没有任何变量或属性对它进行引用时,我们就无法再操作该对象,这种对象就是垃圾。
9.2 JS的垃圾回收机制
JS拥有自动的垃圾回收机制,会自动将垃圾对象从内存中销毁
我们不需要也不能手动进行垃圾回收
我们只需要将不再使用的对象设置为
null
var obj = new Object(); // 使用对象... obj = null; // 标记为垃圾,等待回收9.3 面试题:null一般什么时候使用?
场景 | 说明 |
|---|---|
垃圾回收 | 手动将不需要的变量设置为null |
占位 | 准备放对象但还没想好放什么时,先用null占位 |
清除事件 | 可以用来清除事件绑定 |
十、ES6类语法(class)
10.1 class定义类
ES6提供了更接近传统语言的写法,通过class关键字定义类。
class Person { // 构造函数 constructor(name, age, height) { this.name = name; this.age = age; this.height = height; } // 类方法(存在prototype上) sayHi() { console.log("hi"); } } let p1 = new Person("jack", 18, 180);说明:
使用
class关键字声明类constructor()为构造方法,可以接收参数,初始化实例属性如果没有显式定义,一个空的
constructor()方法会被默认添加this关键字代表实例对象普通方法(如
sayHi())存在于prototype上pn1.constructor === pn1.prototype.constructor返回true
10.2 狗类示例
class Dog { constructor(name, age) { this.name = name; this.age = age; } sayW() { console.log("汪汪"); } } let d1 = new Dog("来福", 2); d1.sayW(); // 汪汪总结
本文系统梳理了JavaScript对象的深入知识:
主题 | 核心要点 |
|---|---|
对象创建方式 | new构造函数、对象字面量、工厂函数、构造函数 |
this指向 | 函数调用→window,方法调用→调用对象,构造函数→新对象 |
构造函数 | 首字母大写、new调用、this添加属性、四步执行流程 |
原型对象 | 公共区域、共享属性和方法、避免污染全局 |
垃圾回收 | 自动回收、将无用对象设为null |
ES6 class | constructor构造方法、方法写在prototype上 |
掌握这些内容,你就真正理解了JavaScript面向对象编程的核心思想。