Skip to content

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>

课件图示 ​

课件第23页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 2.1 Object O

课件第32页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 2.2 Array Ar

课件第5页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 1.1创建对象三种方式(

课件第7页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 1.2 构造函数 l 构

课件第8页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 1.2 构造函数 构造函

课件第9页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 1.2 构造函数 l 构

课件第10页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 总结 总结 1. 构造函

课件第12页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 1.2 构造函数 l n

代码示例 ​

示例一:构造函数创建对象 + 实例成员 / 静态成员 ​

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)。

常见错误 ​

  1. 现象:const p = Pig('佩奇') 得到 undefined 或报错,拿不到实例。
    原因:构造函数未用 new 调用,this 未指向新对象。
    修法:改为 new Pig('佩奇'),并保持构造函数首字母大写。

  2. 现象:构造函数里写 return 123,new Pig() 得到的仍是对象而不是 123。
    原因:new 调用时,构造函数显式返回基本类型会被忽略。
    修法:构造函数内不要用 return 返回基本类型;按 new 规则自动返回实例。

  3. 现象:Pig.eyes 能访问,但 peiqi.eyes 是 undefined;或静态方法里访问不到实例属性。
    原因:静态成员挂在构造函数上,实例成员挂在实例对象上,归属混淆。
    修法:只给构造函数用的属性和方法挂到 Pig 上;需要实例访问的成员挂到实例上,并注意静态方法 this 指向构造函数。

  4. 现象:用 const b = a 或 Object.assign 后修改新对象,原对象也被改;嵌套对象仍相互影响。
    原因:等号赋值复制的是引用;Object.assign 是浅拷贝。
    修法:浅拷贝用 Object.assign({}, a);需要完全独立时使用深拷贝方案。

  5. 现象:Object.key(obj) 报错或拿不到结果;把 Object.keys() 返回值当对象使用。
    原因:方法名是 keys/values,且返回数组。
    修法:使用 Object.keys(obj)、Object.values(obj),再用数组方法处理返回值。

  6. 现象:空数组调用 reduce 报错;求和结果多了初始值;对象累加未给初始值出错。
    原因:reduce 无初始值时首元素作为上一次值,空数组无首元素;有初始值时从初始值开始累加。
    修法:明确传入初始值,如求和给 0、对象累加给 {};空数组必须给初始值。

  7. 现象:toFixed(2) 后参与加法变成字符串拼接。
    原因:toFixed 返回字符串。
    修法:需要数值时用 Number(num.toFixed(2)) 或 parseFloat(...) 转换。

  8. 现象:给字符串临时加属性后读取为 undefined。
    原因:基本类型访问属性时创建包装对象,调用后临时实例被销毁。
    修法:需要持久属性时使用对象;不要依赖给基本类型挂属性。

练习 ​

  1. 改错:function pig(name){ this.name = name } const p = pig('佩奇'),指出问题并改正,要求得到实例对象。

  2. 阅读代码并说明结果:[1, 5, 8].reduce((prev, current) => prev + current, 10);再写出空数组求和的安全写法。

  3. 用构造函数 Book(title, price) 创建两本书;给构造函数添加静态属性 category = '图书';给其中一个实例添加实例方法 read(),并说明实例成员与静态成员的访问方式。

  4. 有对象 const spec = { size: '40厘米×40厘米', color: '黑色', address: '北京' },要求用 Object.values 和 join 拼成 40厘米×40厘米/黑色/北京,并写入页面元素。

  5. 完成字符串处理:把 '传智播客' 反转为 '客播智传';判断是否包含 '播客';把 12.345 保留两位小数并转为数值类型。

原视频对照 ​

#视频时长
1今日内容01:08
2创建对象的三种方式03:33
3构造函数14:06
4new实例化执行过程03:24
5实例成员和静态成员10:10
6一切皆对象06:52
7Object三种静态方法10:16
8Object拼接字符串案例06:24

小结 ​

  1. 批量创建结构相同、值不同的对象时,优先使用构造函数,并坚持首字母大写、用 new 调用。
  2. new 的执行过程决定了 this 指向新实例,并自动返回实例,因此构造函数无需写 return。
  3. 实例成员属于实例对象,彼此独立;静态成员属于构造函数,只能通过构造函数访问。
  4. 静态方法中的 this 指向构造函数,不要在其中直接访问实例属性。
  5. 基本类型通过包装类型临时获得属性和方法,不能把临时属性持久保存在基本类型值上。
  6. Object.keys/values 返回数组,Object.assign 做浅拷贝;不要把等号赋值当成对象拷贝。
  7. 数组处理优先选择语义明确的方法,reduce 必须根据场景明确初始值。
  8. split 负责字符串转数组,join 负责数组转字符串;toFixed 返回字符串,参与运算前需转数值。