认识继承
- 如果 想让这个s这个对象内部有一个name属性,并且可以使用sayName这个方法
- 就可以通过继承的方式让Stu这个类,继承上Person这个类内部的属性与原型上的方法
-
function Person(name) { this.name = name } Person.prototype.sayName = () => { console.log('name') } function Stu(age) { this.age = age } const s = new Stu(18) console.log(s) // {age: 18}
原型继承
-
利用自定义原型的方式去继承
-
当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('QF001') Stu.prototype.abc2 = () => { console.log(456) } const s = new Stu(18) console.log(s) console.log(s.age) console.log(s.name) // QF001
借用构造函数继承
- 核心:借用 call 方法修改 父类构造函数内部的 this 指向
- 优点:将属性继承在自己身上保留了自己的原型
- 缺点:只能继承父类的属性,不能继承父类原型上的方法
function Person(name) {
this.abc = name
this.name = name
}
Person.prototype.sayName = () => {
console.log('name')
}
function Stu(age) {
this.age = age
Person.call(this, 'QF002')
const s = new Stu(18)
// 需求 使用 Person 内部的 name 和 sayName 方法
// console.log(s)
// console.log(s.age)
// console.log(s.abc)
// console.log(s.name)
// console.log(s.__proto__)
s.sayName()
-
-
通过 new 关键字调用 Stu 这个构造函数
-
new 关键会在这个函数内部创建一个空对象, 并且 会把这个函数内部的 this 指定刚才创建的空对象
-
我们现在开始正常执行函数代码
-
3.1给这个对象 添加一个 age 属性, 并且给他赋值 为 参数 age 参数 age === 18
-
3.2调用 Perosn 这个 函数, 并通过 call 方法 改变这个函数内部的 this 指向, this 指向了 刚才 new 关键字创建的 对象
-
3.2.1 Person 函数开始执行
-
给这个对象 添加一个 abc 的属性, 并赋值为 参数 name 参数 name === 'QF002'
-
给这个对象 添加一个 name 的属性, 并赋值为 参数 name 参数 name === 'QF002'
- 现在函数执行完毕, 然后 new关键字 会将刚才创建的 对象 return
- 将这个对象 保存在了 变量 s 中
- 通过上述流程, 我们认为 这个对象中应该是 {age:18, abc: 'QF002', name: 'QF002'}
-
-
组合继承
- 利用原型继承继承到父类的原型上的方法
- 利用借用继承继承到父类的 构造函数内部的 属性
- 好处:
- 能继承到属性, 并且是在自己对象内部(自己身上)
- 能够继承到 父类原型上的方法
- 缺点: 在原型上 有一套多余的属性
-
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()
拷贝继承
- 核心: 通过 for in 遍历父类实例化对象上的所有属性, 拷贝到 子类上
- 它能够遍历 对象内部的 属性, 以及原型上
-
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()
ES6 的继承
- 在书写 子类的时候 语法: class 子类类名 extends 父类类名
- 在书写 子类 constructor 需要在内部书写 super()
- 注意:
- 两个写法必须同时存在,才能完成继承
- super( )必须在constructor在开始位置
-
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()
深浅拷贝
- 一定是引用数据类型 对象,数组,函数(基本只有对象和数组)
- 赋值:只要是引用数据类型,那么在赋值的时候,就是引用地址的传递
浅拷贝
- 遍历对象拿到对象的每一个key与value
- 然后赋值给另外一个对象,如果所有的value 都是基本数据类型, 那么浅拷贝完成之后, 修改新对象不会影响到老对象
- 如果 value 有引用数据类型(出现了多层数据结构), 那么浅拷贝只能拷走第一层数据结构, 多层的没有办法处理
// 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提供的方法 完成浅拷贝
/**
* Object.assign(新对象, 原始对象)
* 将 原始对象 中所有的 key 全部浅拷贝到 新对象中, 然后返回一个 对象
*/
// let o3 = Object.assign(o2, o1)
// 2. 浅拷贝
let o1 = {
a: 1,
b: 2,
c: 3,
d: {
d1: '001',
d2: '002',
d3: '003'
}
}
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 o1 = {
a: 1,
b: 2,
c: 3,
d: {
d1: '001',
d2: '002',
d3: '003'
}
}
// JS 中提供的方法
let o2 = JSON.parse(JSON.stringify(o1))
o2.d.d3 = '9999999'
console.log(o1)
console.log(o2)
函数的定义与创建
- 函数的定义:
- 在 堆内存中 开辟一段内存空间(XF001)
- 把函数体的内容 完全 百分百的 照抄一份 存放在 内存空间中(XF001)
- 把 内存空间的地址(XF001) 赋值给函数名
- 函数的调用:
- 根据函数名内存储的 地址 (XF001) 去堆内存中找到对应函数
- 会去 运行内存中, 开辟一段新的内存, 用于运行函数 (函数作用域)
- 形参赋值---预解析---函数代码全部执行结束
- 函数执行完毕之后, 这段内存空间会被销毁
-
function fn() { let a = 100 console.log(a) } fn()
不会销毁的函数执行空间
- 函数向外部返回了一个引用数据类型(返回的是引用数据类型的地址)
- 外部有一个变量接受这个 返回值 (外部变量中有 变量 拿到 这个 引用地址)
- 为了我们后续能够正常使用, 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 = 'QF001' 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) // 'QF001' res.setA(999) // console.log(res.getA()) // 999
// 2. 调用了 外部函数 outer, 这个函数内部会返回一个对象 obj, 然后我将它存储到了 变量 res2 中
let res2 = outer()
console.log(res2.getA()) // 100
- 案例
- ```javascript
// let count = 1
function outer() {
let a = 1
const obj = {
getA: function () {
return a
},
setA(val) {
a = val
}
}
return obj
}
let res1 = outer() // 内部存储一个对象, 也就是 outer 函数中的 obj 对象
const sub1 = document.querySelector('.sub1')
const inp1 = document.querySelector('.inp1')
const add1 = document.querySelector('.add1')
sub1.onclick = function () {
// count--
// inp1.value = count
let count = res1.getA() // 1
res1.setA(count - 1)
inp1.value = res1.getA()
}
add1.onclick = function () {
// count++
// inp1.value = count
let count = res1.getA() // 1
res1.setA(count + 1)
inp1.value = res1.getA()
}
// let count2 = 1
let res2 = outer() // 内部存储一个对象, 也就是 outer 函数中的 obj 对象
const sub2 = document.querySelector('.sub2')
const inp2 = document.querySelector('.inp2')
const add2 = document.querySelector('.add2')
sub2.onclick = function () {
// count2--
// inp2.value = count2
let count = res2.getA() // 1
res2.setA(count - 1)
inp2.value = res2.getA()
}
add2.onclick = function () {
// count2++
// inp2.value = count2
let count = res2.getA() // 1
res2.setA(count + 1)
inp2.value = res2.getA()
}
沙箱模式的语法糖
-
通过 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)