继承 闭包 沙箱模式
一.原型继承
- 利用自定义原型方式去继承
- 核心: 就子类的原型指向父类的实例化对象
- 优点: 实现了继承
- 缺点: 继承了属性,但是不在自己身上,失去自身的原型
案例:
/*
当Stu这个类继承了Person这个类的时候
我们把Person叫做Stu的父类
把Stu叫做Person的子类
*/
// 1.
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('张三')
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) // QF001
/*
1.去对象内部查找name属性,然后发现对象内部没有这个属性
2.去这个对象内部的__proto__上查找,对象的__proto__指向了自己构造函数的原型
也就是指向了 Stu.prototype
3.因为手动修改了Stu.prototype,给他赋值为了Person构造函数的实例化对象
4.也就是说我们Stu.prototype就指向了Person的实例化对象
5.Person的实例化对象内部有一个属性叫做name,此时找到并返回
*/
s.sayName()
二.借用构造函数继承
- 核心: 借用call方法修改父类构造函数内部this指向
- 优点: 将属性继承在自身,保留了自己原型
- 缺点: 只能继承父类的属性,不能继承父类原型上的方法
案例:
function Person(name) {
this.abc = name
this.name = name
}
Person.prototype.sayName = () => {
console.log('name')
}
// let p1 = new Person('张三')
// console.log(window)
function Stu(age) {
this.age = age
Person.call(this, '李四')
}
const s = new Stu(18)
三.组合继承
-
利用原型继承 继承到父类的原型上的方法
-
利用借用继承 继承到父类的构造函数内部的属性
-
好处:
- 能继承到属性,并且是在自身对象内部
- 能够继承到父类原型上的方法
4.缺点: 在原型上有一套多余属性
案例:
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, '张三')
}
// 1. 利用 原型继承 继承到 父类的 原型上的方法
Stu.prototype = new Person()
const s = new Stu(18)
console.log(s)
console.log(s.name)
/**
* 查找对象内部属性的时候, 会先在对象内部查找, 找到直接使用, 并停止查找
*/
s.sayName()
四.拷贝继承
- 核心: 通过for in 遍历父类实例化对象上的所有属性,拷贝到子类上,它能够遍历对象内部的属性,已经原型上的
案例:
function Person(name) {
this.name = name
}
Person.prototype.sayName = () => {
console.log('name')
}
function Stu(age) {
this.age = age
const p = new Person('张三')
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()
五.ES6类的继承
-
书写子类的时候 语法: class 子类类名 extends 父类类名
-
书写子类constructor 需要在内部书写 super()
-
注意:
- 两个写法必须同时存在,才能完成继承
- super() 必须在 constructor 开始位置
4.类的继承,除了能够继承class类,还能够继承ES5构造函数
案例:
function Person (name) {
this.name = name
}
Person.prototype.sayName = function () {
console.log('name')
}
class Stu extends Person {
constructor(age) {
super('张三')
this.age = age
}
}
const s = new Stu(18)
console.log(s)
console.log(s.age)
console.log(s.name)
s.sayName()
六.浅拷贝与赋值
-
深浅拷贝
- 一定是 引用数据类型
- 对象,数组,函数(基本只有对象和数组
2.赋值
只要是引用数据类型,那么在赋值的时候,就是引用地址的传递
3.浅拷贝
- 遍历对象拿到对象的每一个key与value
- 然后赋值给另外一个对象
- 如果所有的value都是基本数据类型,那么浅拷贝完成之后,修改新对象不会影响到老对象
- 如果value有引用数据类型(出现了多层数据结构),那么浅拷贝只能拷走第一层数据结构,多层没有办法处理
案例:
// 1. 赋值
// const s1 = '123'
// let s2 = s1 // 赋值
// console.log(s2 === s1)
// s2 = '456'
// console.log(s2) // 456
// console.log(s1) // 123
// let o1 = { a: 1 }
// let o2 = o1 // 赋值
// console.log(o2 == o1)
// o2.a = 999
// console.log(o1)
// console.log(o2)
// let a1 = [1, 2, 3]
// let a2 = a1 // 赋值
// console.log(a2 == a1)
// a2[0] = 999
// console.log(a1)
// console.log(a2)
// 2. 浅拷贝
let o1 = {
a: 1,
b: 2,
c: 3,
d: {
d1: '001',
d2: '002',
d3: '003'
}
}
// let o2 = {}
// for (let key in o1) {
// /**
// * 遍历 o1 对象所有的 key, 然后赋值给 对象 o2
// */
// console.log(key, o1[key])
// o2[key] = o1[key]
// }
// console.log(o1) // {a: 1, b: 2, c: 3}
// console.log(o2) // {a: 1, b: 2, c: 3}
// console.log(o1 === o2) // false
// o2.b = 999
// console.log(o1) // {a: 1, b: 2, c: 3}
// console.log(o2) // {a: 1, b: 999, c: 3}
// console.log(o1)
// console.log(o2)
// o2.d.d1 = 'QF666'
// console.log(o1)
// console.log(o2)
// 通过 JS 提供的方法 完成浅拷贝
// console.log(o1)
// console.log(o2)
/**
* Object.assign(新对象, 原始对象)
* 将 原始对象 中所有的 key 全部浅拷贝到 新对象中, 然后返回一个 对象
*/
// let o3 = Object.assign(o2, o1)
let o2 = Object.assign({}, o1)
console.log(o1)
console.log(o2)
七.深拷贝
- 不管数据有多少层数据结构,百分百复制出来一份一模一样但是毫不相关的数据
案例:
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)
八.认识函数的过程
-
定义
- 在堆内存中,开辟一段内存空间(a)
- 把函数体的内容完全百分百照搬,存放在内存空间中(a)
- 把内存空间的地址(a)复制给函数名
-
调用
- 根据函数名内存储的地址(a),去堆内存中找到对应函数
- 会去运行内存中,开辟一段新的内存,用于运行函数(函数作用域)
- 形参复制---预解析---函数代码全部执行结束
- 函数执行完毕之后,这段内存空间会被销毁
九.认识不会被销毁的内存空间
- 函数向外部返回了一个引用函数类型(返回的是引用数据类型的地址)
- 外部有一个变量接受这个 返回值(外部变量中有 变量拿到这个引用地址)
- 为了后续能够正常使用,js不会把这个函数的运行空间销毁
案例:
function fn () {
let obj = {
a: 1,
b: 2
}
return obj
}
let o1 = fn()
// console.log(o1)
o1 = 1000
十.闭包
-
重点: 看清楚调用的是哪个函数
-
构成条件:
- 需要一个不会被销毁的函数执行空间
- 需要直接或间接的返回一个函数
- 内部函数使用着 外部函数的私有变量
-
闭包的好处: 延长变量的作用域(使用时间)
函数外部可以使用函数内部的变量
- 闭包的弊端: 因为闭包的存在就一定代表一个函数执行空间不会被销毁,如果大量使用闭包,会导致内存空间占据严重
案例:
function outer() {
let a = 100
let str = 'QF001'
let obj = {
a: 1,
b: 2
}
function inner() {
return a
}
return inner
}
// 1. 调用 outer 的到了 内层函数 inner, 存到了 变量 res 中
let res = outer()
/**
* 2. 调用 res 也就是 调用 内层函数 inner, 调用完成之后 因为函数内部 return str,
* 所以我的到了一个 字符串
* 然后 我将这个 字符串 存储到了 newStr 这个变量中
*/
let newStr = res()
// 3. 打印 newStr 得了 outer 函数中 str 变量
console.log(newStr) // 100
十一.沙箱模式
- 利用间接返回一个函数,然后去拿到外部函数内的私有变量
案例:
function outer() {
let a = 100
let str = '张三'
const obj = {
getA: function () {
return a
},
getStr: function () {
return str
},
setA(val) {
a = val
}
}
return obj
}
// 1. 调用了 外部函数 outer , 这个函数内部会返回一个 对象 obj, 然后我将她存储到了 变量 res
let res = outer()
console.log(res)
let num = res.getA()
// console.log(num) // 100
let newStr = res.getStr()
// console.log(newStr) // '张三'
res.setA(999)
// console.log(res.getA()) // 999
// 2. 调用了 外部函数 outer, 这个函数内部会返回一个对象 obj, 然后我将它存储到了 变量 res2 中
let res2 = outer()
console.log(res2.getA()) // 100
十二.沙箱模式语法糖
- 沙箱模式语法糖: 再不影响功能的情况下,对我们的语法做一点简化操作 ES6
- 通过 getter 和 setter 帮助我们简化代码书写
案例:
function outer() {
let a = 1
let b = 100
const obj = {
get a () {
return a
},
set a (val) {
a = val
},
get b () {
return b
},
set b (val) {
b = val
}
}
return obj
}
let res = outer()
// console.log(res.getA()) // 这样使用 有问题
console.log(res)
console.log(res.a)
res.a = 999
console.log(res.a)
console.log(res.b)