一、认识继承
// 构造函数1
function Person(name){
this.name = name
}
Person.prototype.init = function(){
console.log('我是Person原型上的方法')
}
// 构造函数2
function Stu(age){
this.age = age
}
Stu.prototype.sayHi = function(){
console.log('你好')
}
const s1 = new Stu(18)
console.log(s1)
console.log(s1.__proto__)
-
如果
s1对象内部具有一个属性为name -
并且
s1还可以使用init方法 -
那么我们就可以说
Stu这个构造函数继承自Person构造函数 -
Stu是Person的子类 -
Person是Stu的父类
二、原型继承
利用自定义原型的方式,实现继承关系
核心:将子类的原型修改为父类的实例化对象
-
优点:可以使用父类的属性和方法,实现了继承
-
缺点:
- 1.原本原型上的方法不能使用了(因为原型对象被改变了)
- 2.继承到的属性并不在自己身上,而是在原型对象上(不影响使用)
// 构造函数1
function Person(name){
this.name = name
}
Person.prototype.init = function(){
console.log('我是Person原型上的方法')
}
// 构造函数2
function Stu(age){
this.age = age
}
Stu.prototype = new Person('张三')
Stu.prototype.sayHi = function(){
console.log('你好')
}
console.log(Stu.prototype)
const s1 = new Stu(18)
console.log(s1)
console.log(s1.name)
s1.init()
s1.sayHi()
访问对象s1的name属性
- 1.先在对象本身查找,找到就使用,但是自身就只有一个
age属性,所以没找到 - 2.所以会去自己的
__proto__对象中查找,也就是自己构造函数的原型对象- 2.1 但是现在自己构造函数的原型对象 已经被我们修改为
Person这个构造函数的实例化对象 - 2.2这个实例化对象上是具有
name属性的,并且还有一个init方法
- 2.1 但是现在自己构造函数的原型对象 已经被我们修改为
- 3.所以在实例化对象中找到了
name属性,值为“张三”
三、借用构造函数继承
-
核心:把父类构造函数当作普通函数调用,并利用
call修改这个函数内部的this指向(如果不修改的话,函数的this指向了其他的对象) -
优点:把父类的属性全部继承在了自己身上
-
缺点:
- 1.只能继承父类的属性,不能继承父类的方法
- 2.每次调用
Stu的时候,Stu内部还会自动调用一次Person函数
//构造函数1
function Person(name) {
this.name = name
}
Person.prototype.init = function () {
console.log('我是Person原型上的方法')
}
//构造函数2
function Stu(age, name) {
// 1.自动创建出来的一个对象(这个函数内部的this就指向了这个对象,所以可以通过this向这个对象上添加属性)
// 2.手动向对象上添加属性
this.age = age
Person.call(this, name) //将Person内部的this修改为了第一步被自动创建出来的对象,并传递一个参数name给Person函数使用
// 3.自动返回这个对象
}
Stu.prototype.sayHi = function () {
console.log('你好')
}
const s1 = new Stu(18, '张三')
console.log(s1)
// s1.init() //没有继承到,所以无法使用
四、组合继承
-
核心:把原型继承与借用构造函数继承结合起来使用
-
优点:自身的对象上具有继承到的属性, 并且能够继承到父类原型上的方法
-
缺点:实例化对象上与原型对象上,都有父类的属性(多了一套属性,但是并不影响使用)
// 构造函数1
function Person(name) {
this.name = name
}
Person.prototype.init = function () {
console.log('我是Person原型上的方法')
}
// 构造函数2
function Stu(age, name) {
this.age = age
//1.借用构造函数继承,得到父类的属性(放在了对象上,并并没有继承父类原型上的方法)
Person.call(this, name)
}
// 2.利用原型继承,得到父类的属性(原型上)与方法
Stu.prototype = new Person('这个字符串没有意义')
Stu.prototype.sayHi = function () {
console.log('你好')
}
// 创建实例化对象
const s1 = new Stu(18, '张三')
console.log('Stu的实例化对象', s1)
console.log('Stu的原型对象', s1.__proto__)
console.log(s1.name) //张三
s1.init()
五、拷贝继承
- 补充:
for...in遍历——可以遍历到对象的原型上的方法
// 构造函数1
function Person(name) {
this.name = name
}
Person.prototype.init = function () {
console.log('我是Person原型上的方法')
}
// const p1 = new Person('张三')
// for (let key in p1) {
// console.log(key, p1[key])
// }
// 构造函数2
function Stu(age, name) {
this.age = age
/**
* 在子类构造函数历史里父类构造函数,得到父类构造函数的实例化对象
*
* 然后利用 for...in 可以遍历到原型上的属性这个特点,将实例化对象的属性与其原型上的方法 一起拷贝到子类构造函数的原型中
*/
const p1 = new Person('张三')
for (let key in p1) {
// console.log(key, p1[key])
Stu.prototype[key] = p1[key] //拷贝继承
}
}
Stu.prototype.sayHi = function () {
console.log('你好')
}
// 创建实例化对象
const s1 = new Stu(18, '张三')
console.log('Stu的实例化对象', s1)
console.log('Stu的原型对象', s1.__proto__)
六、ES6的继承
语法要求:
- 1.书写子类的时候:
class 子类类名 extends 父类类名 {...} - 2.书写
constructor的时候:内部需要书写super('父类需要的参数')
注意:
- 1.
extends和super必须同时出现才能完成继承 - 2.
super必须出现在constructor的第一行
额外扩展:
ES6 类也能继承 ES5 的构造函数
验证方法:
将 Person 更改为 ES5 的构造函数写法即可
//父类
class Person {
constructor(name) {
this.name = name
}
init() {
console.log('我是Person原型上的方法')
}
}
// 子类
class Stu extends Person {
constructor(age) {
super('父类需要的参数,都写在这里边')
this.age = age
}
sayHi() {
console.log('你好~~~')
}
}
const s1 = new Stu(18)
console.log(s1)
console.log(s1.name)
s1.init()
七、深浅拷贝
含义:通常是指将一个引用数据类型,拷贝到另外一个变量中,但是根据拷贝的方法不同,展示出的效果也有差异
- 浅拷贝:将一份数据拷贝到另外一个变量中,修改第一层数据时不会互相影响,但是修改第二层数据时会互相影响
- 深拷贝:一份数据拷贝到另外一个变量中,不管修改哪一层数据,两个对象之间都不会互相影响
// 赋值
// let obj = {
// name: '张三',
// age: 18,
// info: {
// width: 100,
// height: 280
// }
// }
// let obj2 = obj
// obj2.age = 20
// console.log(obj) //因为是直接赋值,所以修改obj2会影响obj,所以它内部的name属性的值也被更改为了20
// 浅拷贝
// let obj = {
// name: '张三',
// age: 18,
// info: {
// width: 100,
// height: 280
// }
// }
// let newObj = {}
// for(let key in obj){
// newObj[key] = obj[key]
// }
// newObj.age = 99
// newObj.info.width = 999
// console.log(newObj)
// console.log(obj)
//深拷贝
let obj = {
name: '张三',
age: 18,
info: {
width: 100,
height: 280
}
}
let newObj = {}
//针对面试
function deepClone(target, origin) {
/**
* target:目标对象
* origin 原始对象
*
* 需求:将原始对象origin内部的所有属性,拷贝到目标对象target中
*/
// 1.通过 for...in 遍历对象,拿到对象的所有属性,拷贝到目标对象target中
for (let key in origin) {
// 2.根据遍历到的这个属性的属性值是什么类型的,决定执行什么代码
if (Object.prototype.toString.call(origin[key]) === '[object Object]') {
// 表明当前这个key的值为一个对象
target[key] = {}
// deepClone('目标对象', '原始对象')
deepClone(target[key], origin[key])
}
else if
(Object.prototype.toString.call(origin[key]) === '[object Array]') {
// 表明当前这个key的值为一个数组
target[key] = []
deepClone(target[key], origin[key])
}
else {
target[key] = origin[key]
}
}
}
// deepClone(newObj, obj)
// newObj.age = 99
// newObj.info.width = 999
// console.log('newObj', newObj)
// console.log('obj', obj)
//针对工作
newObj = JSON.parse(JSON.stringify(obj))
deepClone(newObj, obj)
newObj.age = 99
newObj.info.width = 999
console.log('newObj', newObj)
console.log('obj', obj)
八、函数的定义与调用
1.定义:
- 在堆内存中开一个空间
- 将函数的函数体内的代码保存到堆内存中
- 将堆内存的地址保存在变量名(函数名),最后将这个变量名存储在栈内存中
2.调用:
- 根据变量名(函数名)中的地址,找到对应的函数
- 然后再调用栈中开一个新的空间(函数的执行空间)
- 在执行空间中对函数的形参进行赋值
- 在执行空间中进行变量的预解析
- 在执行空间中执行函数的代码
- 销毁当前函数的执行空间
九、永不销毁的执行空间
- 1.正常书写一个函数
- 2.在这个函数内向外返回一个引用数据类型
- 3.当满足上述条件时,这个函数的执行空间将 不会被销毁
function fn() {
// var a = 100
const obj = {
name: 'fn函数的name',
age: '不知道'
}
return obj
}
// 变量newObj内部保存着fn函数中声明的一个对象obj的地址,所以fn函数就不会被销毁,如果销毁了,那么对象也就无法访问了
const newObj = fn()
console.log(newObj)
// 将newObj的值修改后就与函数内部的对象切断了联系,那么这个函数的执行空间就会被销毁
// newObj = null
闭包
-
1.需要直接或者间接的返回一个函数
-
2.内部函数需要访问外部函数的局部变量
-
好处:
延长变量的生命周期,在函数外可以使用函数内的变量
- 弊端: 执行空间不会被销毁,如果大量使用会造成内存泄漏
function outer() {
let a = 100
let obj = {
name: 'outer函数',
age: '随意'
}
function inner() {
// console.log(a)
// console.log(obj)
// return a
return obj
}
// 1.直接返回一个函数
return inner
}
const newFn = outer()
// console.log(newFn) //得到一个函数(inner)
// newFn()
let num = newFn()
console.log(num)