认识继承
想让某个类可以使用另一个类的某些属性和它原型上的方法,就可以使用继承的方法
1、原型继承
利用自定义原型的方式去继承:
当a这个类继承了b这个类的时候
b叫做a的父类
a叫做b的子类
核心:子类的原型指向父类的实例化对象
a.prototype = new b()
优点:实现了继承
缺点:继承了属性,但是不在自己身上
失去了自己的原型
function Person(name) {
this.name = name
}
Person.prototype.sayName = () => {
console.log('name')
}
// 2.
function Stu(age) {
this.age = age
}
Stu.prototype.abc = () => {
console.log(123)
}
Stu.prototype = new Person('001')
Stu.prototype.abc2 = () => {
console.log(456)
}
const s = new Stu(18)
// 需求 使用 Person 内部的 name 何 sayName 方法
console.log(s)
console.log(s.age)
console.log(s.name) // 001
/**
* 1. 去 对象内部查找 name 属性, 然后发现对象内部没有这个属性
* 2. 去这个对象内部的 __proto__ 上查找, 对象的__proto__ 指向了自己构造函数的 原型
* 也就是 指向了 Stu.prototype
* 3. 因为我们手动修改了 Stu.prototype, 给他赋值为了 Person 构造函数的实例化对象
* 4. 也就是说 我们 Stu.prototype 就指向了 Person的实例化对象
* 5. Person 的实例化对象 内部 有一个属性叫做 name, 此时找到并返回
*/
2、借用构造函数继承
- 核心:借用call方法修改父级构造函数内部this指向
-
语法: 父类.call(this,'参数') 优点:1.将属性继承在自己的身上 2. 保留自己的原型 缺点:只能继承父类的属性,不能继承父类原型上的方法
function Person(name) {
this.abc = name
this.name = name
}
Person.prototype.sayName = () => {
console.log('name')
}
// let p1 = new Person('001')
// console.log(window)
function Stu(age) {
this.age = age
Person.call(this, '002')
/**
* 1. 通过 new 关键字调用 Stu 这个构造函数
* 2. new 关键会在这个函数内部创建一个空对象, 并且 会把这个函数内部的 this 指定刚才创建的空对象
* 3. 我们现在开始正常执行函数代码
* 3.1 给这个对象 添加一个 age 属性, 并且给他赋值 为 参数 age 参数 age === 18
* 3.2 调用 Perosn 这个 函数, 并通过 call 方法 改变这个函数内部的 this 指向, this 指向了 刚才 new 关键字创建的 对象
* 3.2.1 Person 函数开始执行
* + 给这个对象 添加一个 abc 的属性, 并赋值为 参数 name 参数 name === '002'
* + 给这个对象 添加一个 name 的属性, 并赋值为 参数 name 参数 name === '002'
* 4. 现在函数执行完毕, 然后 new关键字 会将刚才创建的 对象 return
*
* 5. 将这个对象 保存在了 变量 s 中
* 6. 通过上述流程, 我们认为 这个对象中应该是 {age:18, abc: '002', name: '002'}
*/
}
const s = new Stu(18)
3、组合继承
1、利用原型继承到父类的原型上的方法
2、利用借用继承继承到父类的构造函数内部属性
3、语法:
优点:1、能继承到属性,并且是在自己对象内部
2、能继承到父类原型的方法
缺点:在原型上有一套多余的属性
function Person(name) {
this.abc = name
this.name = name
}
Person.prototype.sayName = () => {
console.log('name')
}
function Stu(age) {
this.age = age
// 2. 利用 借用继承 继承到 父类的 构造函数内部的 属性
Person.call(this, 'QF001')
}
// 1. 利用 原型继承 继承到 父类的 原型上的方法
Stu.prototype = new Person()
const s = new Stu(18)
console.log(s)
console.log(s.name)
/**
* 查找对象内部属性的时候, 会先在对象内部查找, 找到直接使用, 并停止查找
*/
s.sayName()
4、拷贝继承
核心:通过for in 遍历父类实例化对象上的所有属性,拷贝到子类上。
语法:在子类里面
for(let key in p(父类实例化对象)){
子类.prototype[key] = p[key]
}
function Person(name) {
this.name = name
}
Person.prototype.sayName = () => {
console.log('name')
}
function Stu(age) {
this.age = age
const p = new Person('QF001')
for (let key in p) {
// console.log(key, p[key])
Stu.prototype[key] = p[key]
}
}
Stu.prototype.abc = () => {
console.log(123)
}
const s = new Stu(18)
// 需求 使用 Person 内部的 name 何 sayName 方法
console.log(s)
console.log(s.age)
console.log(s.name)
s.abc()
s.sayName()
5、ES6类的继承
1、在书写子类时
语法:class 子类类名 extends 父类类名
2、在书写 子类 constructor 需要在内部书写 super()
**注意**
1、两个写法必须同时存在,才能完成继承
2、super()必须写在constructor最在开始位置**
**类的继承除了可以继承class类,还能够继承ES5的构造函数**
class Person {
constructor(name) {
this.name = name
}
sayName() {
console.log('name')
}
}
function Person (name) {
this.name = name
}
Person.prototype.sayName = function () {
console.log('name')
}
class Stu extends Person {
constructor(age) {
super('QF001')
this.age = age
}
}
const s = new Stu(18)
console.log(s)
console.log(s.age)
console.log(s.name)
s.sayName()
6、深浅拷贝
一定是引用数据类型(对象,数组,函数(很少))
- 赋值:只要是引用数据类型,复制的时候,就是引用地址的传递
浅拷贝:
遍历对象拿到所有的key和value
赋给另一个对象
如果所有的vlaue都是基本数据类型,那么浅拷贝完成后修改值不会影响到老对象。
如果为引用数据类型,并且出现了多层结构,那么浅拷贝完成后只能靠被第一层的数据结构,多层的没办法处理,修改时还会被同时改变。、
1、自己手写 for in 循环遍历
let o2 = {}
for (let key in o1) {
/**
* 遍历 o1 对象所有的 key, 然后赋值给 对象 o2
*/
console.log(key, o1[key])
o2[key] = o1[key]
}
2、
/**
* Object.assign(新对象, 原始对象)
* 将 原始对象 中所有的 key 全部浅拷贝到 新对象中, 然后返回一个 对象
*/
通过 JS 提供的方法 完成浅拷贝
```js
console.log(o1)
console.log(o2)
let o3 = Object.assign(o2, o1)
let o2 = Object.assign({}, o1)
深拷贝
不管数据有多少层数据结构,百分百复制出来一份一模一样但毫不相关的数据。
// JS 中提供的方法
let o2 = JSON.parse(JSON.stringify(o1))
let o1 = {
a: 1,
b: 2,
c: 3,
d: {
d1: '001',
d2: '002',
d3: '003'
}
}
let o2 = {}
function deepClone(target, origin) {
// 将 origin 完全百分百的复制出来一份, 到 target 中
for (let k in origin) {
// console.log(k, origin[k])
if (origin[k].constructor === Object) {
target[k] = {}
deepClone(target[k], origin[k])
} else if (origin[k].constructor === Array) {
target[k] = []
deepClone(target[k], origin[k])
} else {
target[k] = origin[k]
}
}
}
deepClone(o2, o1)
// console.log(o1)
// console.log(o2)
o2.d.d2 = 'QF666'
console.log(o2)
console.log(o1)
// JS 中提供的方法
let o2 = JSON.parse(JSON.stringify(o1))
o2.d.d3 = '9999999'
console.log(o1)
console.log(o2)
函数的过程
1、定义
在堆内存中开辟一段内存空间
把函数体的内容完全百分百的照抄一份存放在内存空间中
把内存空间的地址赋值给函数名
2、调用
根据函数名内存储的地址去堆内存中找到
会去运行内存中,开辟一段新的内存,用于运行函数(函数作用域)
形参复值--->预解析--->函数代码全部执行结束
函数执行完毕之后,这段内存空间会被销毁
不会被销毁的内容空间
1、函数向外部返回了一个 **引用数据类型**(返回的是应用数据类型的地址)
2. 外部有一个变量接受这个 返回值 (外部变量中有 变量 拿到 这个 引用地址)
3. 为了我们后续能够正常使用, JS 不会把这个函数的运行空间 销毁
闭包
闭包 (重点!!! 看清楚我们调用的是那个函数)
构成条件:
1. 需要一个不会被销毁的函数执行空间
2. 需要 直接 或 间接 的返回一个函数
3. 内部函数使用着 外部函数的私有变量
闭包的好处: 延长变量的作用域(使用时间)
函数外部可以使用函数内部的变量了
闭包的弊端: 因为闭包的存在就一定代表一个函数执行空间不会被销毁, 如果大量使用闭包, 会导致内存空间占据严重
沙箱模式
沙箱模式
利用 间接 返回一个函数, 然后去拿到 外部函数内的私有变量
沙箱模式语法糖:ES6推出的沙箱模式的简写。