【JS进阶-Day2】构造函数与内置对象方法

2 阅读6分钟

【JS进阶-Day2】构造函数与内置对象方法

📺 对应视频:P164-P176 | 🎯 核心目标:掌握构造函数与实例化原理、Object静态方法、数组高阶方法


一、构造函数

1.1 为什么需要构造函数?

当我们需要批量创建相似对象时,构造函数提供了一个模板。

// 问题:重复创建类似对象
const p1 = { name: '张三', age: 18, greet() { console.log(this.name) } }
const p2 = { name: '李四', age: 20, greet() { console.log(this.name) } }
// greet 方法每个对象都有一份,浪费内存

// 解决:构造函数
function Person(name, age) {
  this.name = name   // 实例属性
  this.age = age
}
// 方法放在原型上(共享)
Person.prototype.greet = function() {
  console.log(`我叫${this.name},今年${this.age}岁`)
}

const p1 = new Person('张三', 18)
const p2 = new Person('李四', 20)
p1.greet()  // 我叫张三,今年18岁

1.2 new 关键字做了什么?

// 执行 new Person('张三', 18) 时,JS 做了这4步:
// 1. 创建一个空对象:obj = {}
// 2. 将 obj 的原型指向 Person.prototype:obj.__proto__ = Person.prototype
// 3. 以 obj 作为 this 执行构造函数:Person.call(obj, '张三', 18)
// 4. 返回 obj(如果构造函数没有 return 对象的话)

// 手动实现 new
function myNew(Constructor, ...args) {
  const obj = Object.create(Constructor.prototype)  // 步骤 1+2
  const result = Constructor.apply(obj, args)        // 步骤 3
  return result instanceof Object ? result : obj     // 步骤 4
}

const p = myNew(Person, '张三', 18)
p.greet()  // 我叫张三,今年18岁

1.3 instanceof 运算符

const p = new Person('张三', 18)

p instanceof Person  // true(p 的原型链上有 Person.prototype)
p instanceof Object  // true(所有对象都是 Object 的实例)

// 手动实现 instanceof
function myInstanceof(target, Constructor) {
  let proto = Object.getPrototypeOf(target)
  while (proto !== null) {
    if (proto === Constructor.prototype) return true
    proto = Object.getPrototypeOf(proto)
  }
  return false
}

二、Object 静态方法

2.1 Object.keys / values / entries

const person = { name: '张三', age: 18, gender: '男' }

Object.keys(person)     // ['name', 'age', 'gender'](键数组)
Object.values(person)   // ['张三', 18, '男'](值数组)
Object.entries(person)  // [['name','张三'], ['age',18], ['gender','男']](键值对数组)

// 配合解构遍历
for (const [key, value] of Object.entries(person)) {
  console.log(`${key}: ${value}`)
}

// 对象转 Map
const map = new Map(Object.entries(person))

2.2 Object.assign

// 浅合并对象(后面的覆盖前面的)
const target = { a: 1, b: 2 }
const source = { b: 4, c: 5 }
const result = Object.assign(target, source)
// target = { a: 1, b: 4, c: 5 }(注意:target 被修改!)
// result === target(返回的就是 target)

// 不修改原对象的写法
const merged = Object.assign({}, target, source)

// 拷贝对象(浅拷贝)
const clone = Object.assign({}, person)
// 更推荐:const clone = { ...person }

2.3 Object.freeze / Object.seal

const config = Object.freeze({
  API_URL: 'https://api.example.com',
  TIMEOUT: 5000
})
config.API_URL = 'xxx'   // 静默失败(严格模式报错)
config.newKey = 'abc'    // 无效
delete config.TIMEOUT    // 无效

// freeze vs seal
Object.freeze(obj)  // 不能增删改属性
Object.seal(obj)    // 不能增删属性,但可以改属性值

2.4 Object.create

// 创建指定原型的对象
const proto = {
  greet() { console.log(`我是${this.name}`) }
}

const obj = Object.create(proto)
obj.name = '张三'
obj.greet()  // 我是张三

// Object.create(null):创建没有原型的纯净对象(常用于字典)
const dict = Object.create(null)
dict.hasOwnProperty  // undefined(没有 Object 原型上的方法)

三、数组高阶方法

3.1 forEach(遍历)

const arr = [1, 2, 3, 4, 5]

arr.forEach((item, index, array) => {
  console.log(item, index)
})
// ⚠️ forEach 没有返回值,不能 break/continue,不能 return 跳出
// 需要提前退出用 for...of + break,或 every/some

3.2 map(映射,返回新数组)

const nums = [1, 2, 3, 4, 5]

// 每个元素乘以2
const doubled = nums.map(n => n * 2)   // [2, 4, 6, 8, 10]

// 将对象数组转换结构
const users = [{ name: '张三', age: 18 }, { name: '李四', age: 20 }]
const names = users.map(u => u.name)   // ['张三', '李四']
const cards = users.map(u => ({
  label: u.name,
  value: u.age
}))

// ⚠️ map 必须返回值,否则是 undefined
nums.map(n => { n * 2 })  // [undefined, undefined, ...](少写了 return)

3.3 filter(过滤,返回新数组)

const nums = [1, 2, 3, 4, 5, 6]

const evens = nums.filter(n => n % 2 === 0)  // [2, 4, 6]
const odds = nums.filter(n => n % 2 !== 0)   // [1, 3, 5]

const users = [
  { name: '张三', age: 18, active: true },
  { name: '李四', age: 15, active: false },
  { name: '王五', age: 22, active: true }
]
const activeAdults = users.filter(u => u.active && u.age >= 18)
// [{ name: '张三', ... }, { name: '王五', ... }]

3.4 reduce(累积/聚合)

// arr.reduce(callback, initialValue)
// callback(accumulator, currentValue, index, array)

const nums = [1, 2, 3, 4, 5]

// 求和
const sum = nums.reduce((acc, n) => acc + n, 0)  // 15

// 求乘积
const product = nums.reduce((acc, n) => acc * n, 1)  // 120

// 统计各类出现次数
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
const count = fruits.reduce((acc, fruit) => {
  acc[fruit] = (acc[fruit] || 0) + 1
  return acc
}, {})
// { apple: 3, banana: 2, orange: 1 }

// 数组扁平化(简单版)
const nested = [[1, 2], [3, 4], [5]]
const flat = nested.reduce((acc, arr) => acc.concat(arr), [])  // [1, 2, 3, 4, 5]

// 按属性分组
const people = [
  { name: '张三', dept: '技术' },
  { name: '李四', dept: '产品' },
  { name: '王五', dept: '技术' }
]
const grouped = people.reduce((acc, person) => {
  ;(acc[person.dept] = acc[person.dept] || []).push(person)
  return acc
}, {})
// { '技术': [{...张三}, {...王五}], '产品': [{...李四}] }

3.5 every / some(条件判断)

const nums = [1, 3, 5, 7, 9]

// every:所有元素都满足条件才返回 true
nums.every(n => n > 0)  // true
nums.every(n => n > 5)  // false1,3,5都不大于5)

// some:有任意一个满足条件就返回 true
nums.some(n => n > 5)   // true7,9满足)
nums.some(n => n > 10)  // false

// 实际应用:全选功能
const checkboxes = document.querySelectorAll('.item-check')
const allChecked = [...checkboxes].every(cb => cb.checked)
const someChecked = [...checkboxes].some(cb => cb.checked)

3.6 find / findIndex

const users = [
  { id: 1, name: '张三' },
  { id: 2, name: '李四' },
  { id: 3, name: '王五' }
]

// find:找到第一个满足条件的元素
const user = users.find(u => u.id === 2)      // { id: 2, name: '李四' }
const notFound = users.find(u => u.id === 99) // undefined

// findIndex:找到索引
const idx = users.findIndex(u => u.id === 2)   // 1

3.7 flat / flatMap

// flat:展开嵌套数组
[1, [2, [3, [4]]]].flat()    // [1, 2, [3, [4]]](默认展开1层)
[1, [2, [3, [4]]]].flat(2)   // [1, 2, 3, [4]](展开2层)
[1, [2, [3, [4]]]].flat(Infinity)  // [1, 2, 3, 4](完全展开)

// flatMap:先 map 再 flat(1)
const sentences = ['Hello World', 'Foo Bar']
sentences.flatMap(s => s.split(' '))  // ['Hello', 'World', 'Foo', 'Bar']

四、高阶方法链式调用

const students = [
  { name: '张三', score: 85, class: 'A' },
  { name: '李四', score: 60, class: 'B' },
  { name: '王五', score: 92, class: 'A' },
  { name: '赵六', score: 75, class: 'B' },
  { name: '钱七', score: 55, class: 'A' }
]

// 找出A班及格(>=60)的学生,按分数降序,取前2名的姓名
const result = students
  .filter(s => s.class === 'A' && s.score >= 60)  // 过滤
  .sort((a, b) => b.score - a.score)               // 降序排序
  .slice(0, 2)                                     // 取前2名
  .map(s => s.name)                                // 取姓名
// ['王五', '张三']

五、知识图谱

构造函数与内置对象方法
├── 构造函数
│   ├── 约定:大驼峰命名,方法挂原型
│   ├── new 做了什么:创建→链原型→执行→返回
│   └── instanceof:检测原型链
├── Object 静态方法
│   ├── keys/values/entries(遍历)
│   ├── assign(浅合并/拷贝)
│   ├── freeze/seal(冻结/密封)
│   └── create(指定原型创建)
└── 数组高阶方法
    ├── forEach(遍历,无返回)
    ├── map(映射,返回新数组)
    ├── filter(过滤,返回新数组)
    ├── reduce(聚合,极其强大)
    ├── every/some(条件判断)
    ├── find/findIndex(查找)
    └── flat/flatMap(展开)

六、高频面试题

Q1: map forEach 的区别?

map 有返回值,返回一个新数组,原数组不变;forEach 没有返回值(undefined),常用于有副作用的遍历(如修改DOM、打印日志)。map 适合数据转换,forEach 适合执行操作。

Q2: reduce 如何实现 map filter

// 用 reduce 实现 map
Array.prototype.myMap = function(fn) {
  return this.reduce((acc, item, i) => {
    acc.push(fn(item, i, this))
    return acc
  }, [])
}

// 用 reduce 实现 filter
Array.prototype.myFilter = function(fn) {
  return this.reduce((acc, item, i) => {
    if (fn(item, i, this)) acc.push(item)
    return acc
  }, [])
}

Q3: new 的过程中,如果构造函数 return 了一个对象会怎样?

如果构造函数显式 return 了一个对象new 会返回这个对象而不是新创建的实例;如果 return 的是基本类型(数字、字符串等),则忽略,仍然返回新实例。


⬅️ 上一篇JS进阶Day1 - 闭包与ES6 ➡️ 下一篇JS进阶Day3 - 原型与原型链