主题
3-5构造函数
本页对应 第三章JavaScript进阶,共 8 段视频,约 55 分钟。
学习目标
- 能分别使用对象字面量、
new Object()和自定义构造函数创建对象,并说明开发中优先使用对象字面量的原因。 - 能按构造函数约定编写带参数的构造函数,并解释
new实例化创建空对象、绑定this、执行构造函数代码、返回新对象的四步执行过程。 - 能区分实例成员和静态成员,能通过实例对象访问实例属性与方法、通过构造函数访问静态属性与方法,并说明对应
this的指向。 - 能通过
instanceof验证数组、函数、正则等引用类型属于对象,并能以字符串length、substring为例解释基本数据类型的包装过程,说明“一切皆对象”。 - 能使用
Object.keys()、Object.values()、Object.assign()完成获取对象属性、获取属性值、对象拷贝,并能用Object.values()配合join()将对象属性值拼接后渲染到页面。
知识点
创建对象三种方式
为什么需要:先知道对象不只是“写一个大括号”才能创建,后面才能理解构造函数为什么出现。
怎么想:字面量最直观,new Object 了解即可,第三种构造函数用于批量创建相似对象。
怎么做:笔记代码演示前两种:
html
<body>
<script>
// 1. 利用字面量创建对象(常用的一种方式)
const o = {
name: '佩奇'
}
console.log(o)
// 2. 利用 new Object 创建对象 (了解)
// const oo = new Object({ name: '佩奇' })
const oo = new Object()
oo.name = '佩奇'
console.log(oo)
// 3. 利用构造函数创建对象
</script>调试时先打印 o,再打印 oo,都能得到对象;new Object() 也可以先创建空对象再追加 name。
易错点:Object 的 O 要大写;开发中更常用字面量,new Object 写起来更繁琐,了解即可。
构造函数
为什么需要:创建佩奇后,再创建乔治、猪爸爸、猪妈妈时,属性结构相同却要重复写很多遍。
怎么想:把公共属性抽取到函数里,用参数接收不同值,再用 new 调用生成对象。
怎么做:
html
<body>
<script>
// 构造函数:是一种特殊的函数,用来创建对象(并完成初始化对象)
// 1. 构造函数的2个约定 首字母要大写, 利用new关键字来调用函数
// function Pig() {
// }
// console.log(new Pig)
// console.log(Pig())
function Pig(name, age, gender) {
// this.name 属性
// name 是形参也就是属性值
this.name = name
this.age = age
this.gender = gender
// return 123
}
const peiqi = new Pig('佩奇', 6, '女')
console.log(peiqi)
const qiaozhi = new Pig('乔治', 3, '男')
console.log(qiaozhi)
// 2. 构造函数创建对象说明
// 2.1 new 关键字 实例化对象
// 2.2 如果构造函数没有参数,则可以省略小括号
// 2.3 构造函数里面无需写 return
// 2.4 new Object() new Date() 也是在实例化对象
</script>
</body>转写稿里的调试过程很关键:先写空函数,console.log(new Pig) 得到空对象,而 console.log(Pig()) 并不是对象,说明构造函数要靠 new 调用;再加上 this.name = name 等代码,new Pig('佩奇',6,'女') 就能生成带属性的对象。
易错点:构造函数首字母大写只是约定;this.name 的 name 是属性,右边的 name 是形参;构造函数内部不用写 return,写普通值也不会改变返回的新对象。
new 实例化过程
为什么需要:理解“为什么加了 new 就能创建对象”,才能写对 this。
怎么想:把 new Pig(...) 拆成四步:创建新空对象 → 构造函数中的 this 指向新对象 → 执行构造函数代码 → 返回新对象。
怎么做:执行 this.name = name 时,相当于给新对象添加 name 属性;this.age、this.gender 同理。执行完后再打印 peiqi,就能看到完整对象。
易错点:this 指向的是新对象;没有 new 时不能按构造函数的方式得到新对象;不要依赖构造函数里的 return 返回对象。
实例成员与静态成员
为什么需要:区分“实例对象身上的”和“构造函数身上的”属性和方法,后面读内置对象才不会混。
怎么想:实例成员属于实例对象,彼此独立;静态成员属于构造函数,只能通过构造函数访问。
怎么做:实例成员示例:
html
<body>
<script>
// 实例成员和静态成员
// 1. 实例成员:实例对象上的属性和方法属于实例成员
function Pig(name) {
this.name = name
}
const peiqi = new Pig('佩奇')
const qiaozhi = new Pig('乔治')
peiqi.name = '小猪佩奇' // 实例属性
peiqi.sayHi = () => { // 实例方法
console.log('hi~~')
}
console.log(peiqi)
console.log(qiaozhi)
console.log(peiqi === qiaozhi)
</script>调试可见:peiqi === qiaozhi 为 false;修改 peiqi.name 不影响 qiaozhi,给 peiqi 加 sayHi 也不会加到 qiaozhi 上。
静态成员示例:
html
<script>
// 2. 静态成员 : 构造函数上的属性和方法称为静态成员
function Pig(name) {
this.name = name
}
Pig.eyes = 2 // 静态属性
Pig.sayHi = function () { // 静态方法
console.log(this)
}
Pig.sayHi()
console.log(Pig.eyes) // 2
</script>易错点:静态成员只能构造函数访问;静态方法里用普通函数时,this 指向调用它的构造函数,转写稿特别提醒这里不要写箭头函数。
一切皆对象与包装类型
为什么需要:解释字符串明明是基本类型,为什么能用 length、substring。
怎么想:引用类型本质属于对象;基本类型有包装类型,底层临时包装成对象再调用方法。
怎么做:笔记代码展示包装过程:
html
<body>
<script>
// 包装类型
// const str = 'andy'
// console.log(str.length)
// 包装过程
// const str = new String('andy')
// str.substring()
// str = null
</script>推导:str.length 底层相当于先创建 String 实例,调用实例上的属性或方法,再销毁实例。
易错点:包装是底层临时过程,不是把基本类型永久变成对象;不要手动模仿销毁过程,理解“基本类型也能使用属性和方法”即可。
Object:用静态方法处理对象
为什么需要:对象常用来组织一组数据,若想快速拿到全部属性、全部属性值,或复制一份对象,手写遍历和逐个赋值都麻烦。怎么想:Object 是内置构造函数,推荐用字面量创建对象;需要批量处理对象时,用只有构造函数才能调用的静态方法。怎么做:Object.keys() 返回属性名数组,Object.values() 返回属性值数组,Object.assign(目标对象, 源对象) 把源对象拷贝到目标对象。易错点:assign 是拷贝,拷贝后是两个不同对象,修改新对象不会影响原对象;不要用等号直接赋值,否则只是复制地址,两个变量会相互影响。
html
<body>
<script>
// Object 三种静态方法
const o = { name: '佩奇', age: 6, gender: '女' }
// 1. Object.keys() 得到对象所有属性(重点) 返回的是数组
const key = Object.keys(o)
console.log(key)
// 2. Object.values() 得到对象所有属性值(重点)返回的是数组
const value = Object.values(o)
console.log(value)
// 3. Object.assign(目标对象, 源对象) 对象的拷贝 assign 赋值的意思
const oo = {}
Object.assign(oo, o)
oo.name = '小猪佩奇'
console.log(oo)
console.log(o)
// 3.1 注意 拷贝对象之后是两个不同的对象,不会相互影响
</script>
</body>Array:用 reduce 做累计
为什么需要:数组经常要做遍历、过滤、映射、拼接、查找和累计求和。怎么想:推荐用字面量创建数组,不用 Array 构造函数;reduce 适合把一组值“归并”成一个结果,例如累加。怎么做:arr.reduce(function(上一次值, 当前值){}, 初始值)。没有初始值时,第一次循环用第一个元素作为上一次值;有初始值时,第一次循环用初始值参与计算。易错点:回调必须 return 累计结果;有无初始值会影响第一次循环的“上一次值”。笔记中其他重点实例方法还有 forEach、filter、map、join、find、every、some、concat、sort、splice、reverse、findIndex。
html
<body>
<script>
// 数组reduce方法
// arr.reduce(function(上一次值, 当前值){}, 初始值)
// const arr = [1, 5, 8]
// 1. 没有初始值
// const total = arr.reduce(function (prev, current) {
// return prev + current
// })
// console.log(total)
// 上一次值 当前值 返回值 (第一次循环)
// 1 5 6
// 上一次值 当前值 返回值 (第二次循环)
// 6 8 14
const arr = [1, 5, 8]
// 2. 有初始值
const total = arr.reduce(function (prev, current) {
return prev + current
}, 10)
console.log(total)
// 上一次值 当前值 返回值 (第一次循环)
// 10 1 11
// 上一次值 当前值 返回值 (第二次循环)
// 11 5 16
// 上一次值 当前值 返回值 (第三次循环)
// 16 8 24
</script>
</body>String:拆分、反转、拼接
为什么需要:字符串和数组经常互相转换,比如把“小米,华为,苹果”拆成数组,或把数组拼回字符串。怎么想:split 把字符串转数组,数组可用 reverse 反转,join 再把数组转字符串。怎么做:str.split('') 按空字符串拆成字符数组;str1.split(',') 按逗号拆分;反转“传智播客”可写成 str.split('').reverse().join('')。易错点:split 的分隔符要和原字符串一致;reverse 是数组方法,字符串不能直接调用;join 的分隔符决定拼接结果。String 还可当普通函数用,作用是强制转换成字符串类型。
html
<body>
<script>
// 字符串方法split
const str = '传智播客'
// 1. split('分隔符') 把字符串转换为数组
console.log(str.split(''))
const str1 = '小米,华为,苹果'
console.log(str1.split(','))
// 2. join('分隔符')可以把数组转换为字符串
// 3. 把传智播客这字符串做一个翻转 变成 客播智传
// 把字符串转换为数组, 数组里面reverse翻转,再把数组转换为字符串
console.log(str.split('').reverse())
console.log(str.split('').reverse().join(''))
</script>
</body>Number:控制小数位
为什么需要:价格、尺寸等数值经常要统一显示小数位。怎么想:推荐用字面量声明数值;需要格式化小数时用实例方法 toFixed。怎么做:num.toFixed(2) 保留两位小数,num1.toFixed(2) 对整数会补成 12.00。易错点:toFixed 返回的是字符串;小数位参数表示保留几位,不是截断到第几位。
html
<body>
<script>
// 数字 toFixed 方法
const num = 12.345
console.log(num.toFixed(2)) // 12.35
console.log(num.toFixed(1)) // 12.3
const num1 = 12
console.log(num1.toFixed(2)) // 12.00
</script>
</body>课件图示








代码示例
示例一:构造函数创建对象 + 实例成员 / 静态成员
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>构造函数与成员</title>
</head>
<body>
<script>
// 构造函数:首字母大写,配合 new 调用
function Pig(name, age, gender) {
this.name = name // this.name 是属性,name 是形参(属性值)
this.age = age
this.gender = gender
// return 123 // 构造函数无需 return,写了也没用
}
const peiqi = new Pig('佩奇', 6, '女')
const qiaozhi = new Pig('乔治', 3, '男')
console.log(peiqi, qiaozhi)
console.log(peiqi === qiaozhi) // false:两个不同的实例对象
// 实例成员:实例对象上的属性和方法,彼此独立互不影响
peiqi.name = '小猪佩奇'
peiqi.sayHi = () => { console.log('hi~~') }
peiqi.sayHi()
console.log(peiqi)
console.log(qiaozhi)
// 静态成员:构造函数上的属性和方法,只能由构造函数访问
Pig.eyes = 2
Pig.sayHi = function () { console.log(this) } // this 指向构造函数 Pig
Pig.sayHi()
console.log(Pig.eyes) // 2
console.log(peiqi.eyes) // undefined,实例访问不到静态成员
</script>
</body>
</html>关键行与易错点
this.name = name:左边是属性,右边是形参(属性值),两者不要混为一谈。- 构造函数两个约定:首字母大写、必须用
new调用。直接Pig()调用返回的是undefined,不会创建对象。 new实例化四步:创建空对象 →this指向新对象 → 执行构造函数代码 → 返回新对象;因此构造函数内无需写return,写了普通值也会被忽略。peiqi.name = '小猪佩奇'、peiqi.sayHi = ...加在实例身上,属于实例成员,不会影响qiaozhi。Pig.eyes = 2、Pig.sayHi = function(){}加在构造函数身上,属于静态成员,只能用Pig.eyes访问;静态方法里的this指向构造函数本身(所以这里不能用箭头函数)。- 无参实例化可省略小括号,但习惯上仍写成
new Pig()。
示例二:Object 三种静态方法 + 对象属性值拼接字符串
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Object 静态方法</title>
</head>
<body>
<div class="spec"></div>
<script>
const o = { name: '佩奇', age: 6, gender: '女' }
// 1. Object.keys() 得到对象所有属性,返回数组
const key = Object.keys(o)
console.log(key) // ['name', 'age', 'gender']
// 2. Object.values() 得到对象所有属性值,返回数组
const value = Object.values(o)
console.log(value) // ['佩奇', 6, '女']
// 3. Object.assign(目标对象, 源对象) 对象的拷贝
const oo = {}
Object.assign(oo, o)
oo.name = '小猪佩奇'
console.log(oo) // 修改拷贝后的对象
console.log(o) // 原对象不受影响
// 案例:把对象属性值拼成字符串写入 div
const spec = { size: '40厘米*40厘米', color: '黑色' }
// 方式一:对象点属性拼接(属性多了要一直加,繁琐)
// document.querySelector('.spec').innerHTML = spec.size + '/' + spec.color
// 方式二:Object.values + join(属性再多也不用改代码)
document.querySelector('.spec').innerHTML = Object.values(spec).join('/')
</script>
</body>
</html>关键行与易错点
Object的O必须大写,三个都是静态方法,只能用Object.xxx()调用,不能写成实例方法。Object.keys()/Object.values()返回值都是数组,拿到数组后可以用forEach、map等继续处理。Object.assign(目标对象, 源对象)是“把右边给左边”,顺序不能写反;拷贝后是两个不同的对象,改其中一个不影响另一个。- 千万不要用
oo = o直接赋值,那只是复制地址,两个变量会互相影响——这才需要用assign。 - 拼接案例中,方式一属性越多越繁琐;
Object.values(spec).join('/')一行搞定,新增属性自动拼接,是更推荐写法。
示例三:数组 reduce 求和 + 字符串 split / reverse / join + toFixed
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>数组与字符串常用方法</title>
</head>
<body>
<script>
// 1. reduce 求和:arr.reduce(function(上一次值, 当前值){}, 初始值)
const arr = [1, 5, 8]
const total = arr.reduce(function (prev, current) {
return prev + current
}, 10)
console.log(total) // 24:10+1=11 → 11+5=16 → 16+8=24
// 2. split / reverse / join 实现字符串翻转
const str = '传智播客'
console.log(str.split('')) // ['传','智','播','客']
console.log(str.split('').reverse()) // 翻转后的数组
console.log(str.split('').reverse().join('')) // 客播智传
// 3. split 把逗号分隔的字符串转成数组
const str1 = '小米,华为,苹果'
console.log(str1.split(','))
// 4. toFixed 设置保留小数位(返回字符串)
const num = 12.345
console.log(num.toFixed(2)) // '12.35'
console.log(num.toFixed(1)) // '12.3'
const num1 = 12
console.log(num1.toFixed(2)) // '12.00'
</script>
</body>
</html>关键行与易错点
reduce的第二个参数是初始值:不传时第一次循环的prev就是数组第一个元素;传了10后第一次循环就是10 + 1,结果不同。reduce回调里必须return累加结果,漏写return会得到undefined。split('')用空字符串才是按字符拆;split(',')才按逗号拆,分隔符写错就拆不开。split('').reverse().join('')三步顺序不能变:reverse是数组方法,字符串没有;join才是数组转字符串。toFixed返回的是字符串,不是数字,参与运算前要注意;12.toFixed(2)会因数字字面量歧义报错,用变量num1.toFixed(2)或(12).toFixed(2)。
常见错误
现象:
const p = Pig('佩奇')得到undefined或报错,拿不到实例。
原因:构造函数未用new调用,this未指向新对象。
修法:改为new Pig('佩奇'),并保持构造函数首字母大写。现象:构造函数里写
return 123,new Pig()得到的仍是对象而不是123。
原因:new调用时,构造函数显式返回基本类型会被忽略。
修法:构造函数内不要用return返回基本类型;按new规则自动返回实例。现象:
Pig.eyes能访问,但peiqi.eyes是undefined;或静态方法里访问不到实例属性。
原因:静态成员挂在构造函数上,实例成员挂在实例对象上,归属混淆。
修法:只给构造函数用的属性和方法挂到Pig上;需要实例访问的成员挂到实例上,并注意静态方法this指向构造函数。现象:用
const b = a或Object.assign后修改新对象,原对象也被改;嵌套对象仍相互影响。
原因:等号赋值复制的是引用;Object.assign是浅拷贝。
修法:浅拷贝用Object.assign({}, a);需要完全独立时使用深拷贝方案。现象:
Object.key(obj)报错或拿不到结果;把Object.keys()返回值当对象使用。
原因:方法名是keys/values,且返回数组。
修法:使用Object.keys(obj)、Object.values(obj),再用数组方法处理返回值。现象:空数组调用
reduce报错;求和结果多了初始值;对象累加未给初始值出错。
原因:reduce无初始值时首元素作为上一次值,空数组无首元素;有初始值时从初始值开始累加。
修法:明确传入初始值,如求和给0、对象累加给{};空数组必须给初始值。现象:
toFixed(2)后参与加法变成字符串拼接。
原因:toFixed返回字符串。
修法:需要数值时用Number(num.toFixed(2))或parseFloat(...)转换。现象:给字符串临时加属性后读取为
undefined。
原因:基本类型访问属性时创建包装对象,调用后临时实例被销毁。
修法:需要持久属性时使用对象;不要依赖给基本类型挂属性。
练习
改错:
function pig(name){ this.name = name } const p = pig('佩奇'),指出问题并改正,要求得到实例对象。阅读代码并说明结果:
[1, 5, 8].reduce((prev, current) => prev + current, 10);再写出空数组求和的安全写法。用构造函数
Book(title, price)创建两本书;给构造函数添加静态属性category = '图书';给其中一个实例添加实例方法read(),并说明实例成员与静态成员的访问方式。有对象
const spec = { size: '40厘米×40厘米', color: '黑色', address: '北京' },要求用Object.values和join拼成40厘米×40厘米/黑色/北京,并写入页面元素。完成字符串处理:把
'传智播客'反转为'客播智传';判断是否包含'播客';把12.345保留两位小数并转为数值类型。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 今日内容 | 01:08 |
| 2 | 创建对象的三种方式 | 03:33 |
| 3 | 构造函数 | 14:06 |
| 4 | new实例化执行过程 | 03:24 |
| 5 | 实例成员和静态成员 | 10:10 |
| 6 | 一切皆对象 | 06:52 |
| 7 | Object三种静态方法 | 10:16 |
| 8 | Object拼接字符串案例 | 06:24 |
小结
- 批量创建结构相同、值不同的对象时,优先使用构造函数,并坚持首字母大写、用
new调用。 new的执行过程决定了this指向新实例,并自动返回实例,因此构造函数无需写return。- 实例成员属于实例对象,彼此独立;静态成员属于构造函数,只能通过构造函数访问。
- 静态方法中的
this指向构造函数,不要在其中直接访问实例属性。 - 基本类型通过包装类型临时获得属性和方法,不能把临时属性持久保存在基本类型值上。
Object.keys/values返回数组,Object.assign做浅拷贝;不要把等号赋值当成对象拷贝。- 数组处理优先选择语义明确的方法,
reduce必须根据场景明确初始值。 split负责字符串转数组,join负责数组转字符串;toFixed返回字符串,参与运算前需转数值。