一、认识继承
- 一个构造函数的实例使用另一个构造函数的属性和方法
function Person(name) {
this.name = name
}
Person.prototype.sayHi = function () {
console.log('hello world')
}
function Student() {}
Student.prototype.study = function () {
console.log('好好学习')
}
/**
* 如果 s1 内带有 name 属性
* 并且 s1 还可使用 sayHi 这个方法
* 那么我们就说 Studen 这个类继承自 Person
* Student 是 Person 的子类
* Person 是 Student 的父类
*/
const s1 = new Student()
1、原型继承
- 利用自定义原型的方式来实现继承关系
- 核心: 子类的原型指向父类的实例
- 优点: 可以继承父类的 属性(构造函数体内) 和 方法(构造函数原型)
- 缺点:
- 没有自己的原型
- 继承下来的属性不在自己身上, 在原型上
- 注意:原型继承后, 如果需要在 原型上添加方法, 一定要在原型继承后添加, 不要再原型继承前添加
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () {
console.log('hello world')
}
// 子类
function Student (gender, name) {
this.gender = gender
this.name = name
}
// 实现继承
Student.prototype = new Person('Jack', 18)
/**
* Student 的 prototype 指向了当前构造函数的原型对象
* 原型链继承就是更改他的原型对象为另一个 构造函数的实例对象
*
* 那么后续在查找的时候, 会先在 Student 的实例身上查找
* 找到就是用, 没有的话会去 __proto__ 中查找
* 也就是自己构造函数的原型, 但是现在原型已经被修改为了 Person 构造函数的实例化对象
*
* 所以相当于可以在 Student 的实例上找到或使用 Person 的实例上的方法
*/
// 子类创建实例
const s = new Student('男', 'Jack')
console.log(s)
console.log(s.gender)
console.log(s.name)
console.log(s.age)
s.sayHi()
2、借用构造函数继承
- 借用构造函数继承 / 借用继承 / call 继承
- 核心:
- 把父类构造函数当作普通函数来调用, 利用 call 修改 this 指向
- 优点:
- 把属性都继承在自己身上
- 缺点:
- 只能继承父类的属性(构造函数体内)不能继承方法(原型上的内容)
- 可以有自己的原型
function Person(name, age) {
this.name = name
this.age = age
}
// Person 原型上的方法是给 Person 的实例对象使用
Person.prototype.sayHi = function () { console.log('hello world') }
function Student(gender, ...arg) {
this.gender = gender
// 实现继承
Person.call(this, ...arg)
/**
* Person 是一个构造函数
* 本质上还是一个函数, 所以是可以不加 new 去调用的
* 不加 new 内部无法自动创建对象, 所以调用时 内部 this 指向 window
* 相当于是给 window 添加了 name 和 age 属性
* 我们可以通过 修改 this 指向的方式, 去将构造函数体内的代码继承到函数体内部
*/
}
const s = new Student('男', 'Jack', 18)
console.log(s)
console.log(s.gender)
console.log(s.name)
console.log(s.age)
// s.sayHi()
const s2 = new Student('女', 'Rose', 20)
console.log(s2)
3、组合继承
- 把原型继承 和 借用构造函数继承 放在一起使用
- 优点:
- 能继承 属性和方法
- 继承的属性在自己身上
- 缺点:
- 原型上多了一套属性
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
function Student(gender, ...arg) {
this.gender = gender
// 组合借用继承 - 为了继承构造函数体内的属性
Person.call(this, ...arg)
}
// 组合原型继承 - 为了继承原型上的方法
Student.prototype = new Person()
const s = new Student('男', 'Jack', 18)
console.log(s)
4、拷贝继承
- 拷贝继承
- 利用 for in 循环遍历对象
- 把所有的内容复制一份放到子类的原型上
for in 循环的时候, 不光可以便利到对象自己身上的属性, 也可以遍历原型上的属性
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
function Student(gender, ...arg) {
this.gender = gender
const p = new Person(...arg)
for (let k in p) {
Student.prototype[k] = p[k]
}
}
5、ES6的继承
- 类继承的语法
- 语法:
- 书写子类的时候
- class 子类 extends 父类 {}
- 书写子类的 contructor 的时候
- super(参数)
- 书写子类的时候
- 注意:
- extends 和 super 必须都出现才能正常继承
- 在 constructor 内的时候, 必须先写 super 后写自己的
- ES6 的类可以继承 ES5 的构造函数
- 语法:
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
// 创建一个类 - 创建一个继承自 Person 的类
// 语法: class 类名 extends 父类 {}
class Student extends Person {
constructor(gender, ...arg) {
// 语法: spuer()
super(...arg)
// 自己的属性
this.gender = gender
}
// 自己的方法
study() { console.log('好好学习') }
}
const s = new Student('男', 'Jack', 18)
console.log(s)
二、深浅拷贝
- 把一个数据结构内的所有内容复制一份放在另一个一摸一样的数据结构内
- 注意:
- 表示拷贝的是数据结构, 不是方法(一般不考虑函数, 通常我们都是考虑的数组和对象)
1、赋值
```js
const o1 = { name: 'jack' }
const o2 = o1
console.log(o1, o2)
o2.name = 'Rose'
console.log(o1, o2)
```
2、浅拷贝
1)方法一
const o1 = { name: 'Jack', age: 18, info: { weight: 180, height: 180 } }
const o2 = {}
for (let k in o1) {
o2[k] = o1[k]
}
console.log(o1, o2)
o2.name = 'Rose'
console.log(o1, o2)
// 修改第二层数据
o2.info.weight = 200
console.log(o1, o2)
2)方法二
- Object 上有一个方法, 就是进行浅拷贝, 复制
- 语法: Object.assign(新对象, 原始对象)
- 返回值: 把原始对象内的成员浅拷贝的新对象内
const o1 = { name: 'Jack', age: 18, info: { weight: 180, height: 180 } }
const o2 = Object.assign({}, o1)
console.log(o1, o2)
o2.name = 'Rose'
console.log(o1, o2)
// 修改第二层数据
o2.info.weight = 200
console.log(o1, o2)
3、深拷贝
- 不管多少层数据结构, 都百分之百复制一份过来; 变成两个完全一模一样但是毫不相干的数据结构
- JSON.parse(JSON.stringify(数据)) , 也可以完成深拷贝
1)面试版
const obj = {
name: '我是对象obj',
info: {
width: 100,
height: 101
},
arr: [100, 200, 300]
}
const newObj = {}
function deepCopy(target, origin) {
/**
* target 目标对象
* origin 原对象
*
* 当前函数执行完毕后, 会基于origin 创建一个和他一摸一样的对象, 放到 target 中
*/
// 下边两个写法功能一样, 但是 第一行操作的是形参不依赖外界变量, 第二行操作的是全局变量, 需要操作外界的变量
// target.name = '函数内部添加的'
// newObj.name = '12345678'
for (let key in origin) {
// console.log(key, origin[key])
if (Object.prototype.toString.call(origin[key]) === '[object Object]') {
// 说明当前的值是 对象
target[key] = {}
deepCopy(target[key], origin[key])
} else if (Object.prototype.toString.call(origin[key]) === '[object Array]') {
// 说明当前的值是 数组
target[key] = []
deepCopy(target[key], origin[key])
} else {
// 说明当前的值是 基本数据类型
target[key] = origin[key]
}
}
}
deepCopy(newObj, obj)
修改基本数据类型
newObj.name = '新的名字'
obj.name = '一个更新的名字'
修改引用数据类型
newObj.info.width = 999
obj.arr[0] = 'QF001'
console.log('原对象obj: ', obj)
console.log('深拷贝后的对象newObj: ', newObj)
2)工作版
const obj = {
name: '我是对象obj',
info: {
width: 100,
height: 101
},
arr: [100, 200, 300]
}
// 深拷贝 (工作版)
const newObj = JSON.parse(JSON.stringify(obj))
// 修改基本数据类型
newObj.name = '新的名字'
obj.name = '一个更新的名字'
// 修改引用数据类型
newObj.info.width = 999
obj.arr[0] = 'QF001'
console.log('原对象obj: ', obj)
console.log('深拷贝后的对象newObj: ', newObj)