认识继承
- 如果想让某个对象内部有其他构造函数的属性, 并且可以使用构造函数其中的原型空间,就可以通过继承的方式,让这个类, 继承上另外一个类内部的属性与原型上的方法
原型继承
- 核心: 就子类的原型指向父类的实例化对象
- 优点: 实现了继承
- 缺点:
-
继承了属性, 但是不在自己身上
-
失去了自己的原型
function Person(name) { this.name = name } Person.prototype.sayName = () => { console.log('name') } 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.age) // 18 console.log(s.name) // QF001 -
- 去 对象内部查找 name 属性, 然后发现对象内部没有这个属性
-
- 去这个对象内部的 proto 上查找, 对象的__proto__ 指向了自己构造函数的 原型 也就是 指向了 Stu.prototype
-
- 因为我们手动修改了 Stu.prototype, 给他赋值为了 Person 构造函数的实例化对象
-
- 也就是说 我们 Stu.prototype 就指向了 Person的实例化对象
-
- Person 的实例化对象 内部 有一个属性叫做 name, 此时找到并返回
-
借用构造函数继承
- 核心: 借用 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, 'abc') } const s = new Stu(18) 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 === 'abc'
- 给这个对象 添加一个 name 的属性, 并赋值为 参数 name 参数 name === 'abc'
- 3.2.1 Person 函数开始执行
-
- 现在函数执行完毕, 然后 new关键字 会将刚才创建的 对象 return
-
- 将这个对象 保存在了 变量 s 中
-
- 通过上述流程, 我们认为 这个对象中应该是 {age:18, abc: 'abc', name: 'abc'}
-
组合继承
-
- 利用 原型继承 继承到 父类的 原型上的方法
-
- 利用 借用继承 继承到 父类的 构造函数内部的 属性
-
好处:
-
- 能继承到属性, 并且是在自己对象内部(自己身上)
-
- 能够继承到 父类原型上的方法
-
-
缺点: 在原型上 有一套多余的属性
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, 'abc') } // 1. 利用 原型继承 继承到 父类的 原型上的方法 Stu.prototype = new Person() const s = new Stu(18) 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('abc') for (let key in p) { Stu.prototype[key] = p[key] } } Stu.prototype.abc = () => { console.log(123) } const s = new Stu(18) // 使用 Person 内部的 name 何 sayName 方法 console.log(s.age) console.log(s.name) s.abc() s.sayName()
-
ES6 的继承
-
- 在书写 子类的时候 语法: class 子类类名 extends 父类类名
-
- 在书写 子类 constructor 需要在内部书写 super()
- 注意:
-
- 两个写法 必须同时存在, 才能完成继承
-
- super() 必须在 constructor 在开始位置,
-
-
类的继承, 除了能够继承 calss 类, 还能够继承 ES5 的构造函数
function Person (name) { this.name = name } Person.prototype.sayName = function () { console.log('name') } class Stu extends Person { constructor(age) { super('abc') this.age = age } } const s = new Stu(18) console.log(s.age) console.log(s.name) s.sayName()
-
-
深浅拷贝
-
一定是引用数据类型,对象, 数组, 函数(基本只有对象和数组)
-
赋值
- 只要是引用数据类型, 那么在赋值的时候, 就是引用地址的传递
浅拷贝
-
遍历对象拿到对象的每一个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, b: 2, c: 3, d: { d1: '001', d2: '002', d3: '003' } } let o2 = Object.assign({}, o1) console.log(o1) console.log(o2)
深拷贝
- 不管数据有多少层数据结构, 百分百复制出来一份 一摸一样但是毫不相关的数据
函数的定义与创建
- 函数的过程
-
- 定义
- 在 堆内存中 开辟一段内存空间(XF001)
- 把函数体的内容 完全 百分百的 照抄一份 存放在 内存空间中(XF001)
- 把 内存空间的地址(XF001) 赋值给函数名
-
- 调用
- 根据函数名内存储的 地址 (XF001) 去堆内存中找到对应函数
- 会去 运行内存中, 开辟一段新的内存, 用于运行函数 (函数作用域)
- 形参赋值---预解析---函数代码全部执行结束
- 函数执行完毕之后, 这段内存空间会被销毁
-
不会销毁的函数执行空间
-
- 函数向外部返回了一个引用数据类型(返回的是引用数据类型的地址)
-
- 外部有一个变量接受这个 返回值 (外部变量中有 变量 拿到 这个 引用地址)
-
-
为了我们后续能够正常使用, JS 不会把这个函数的运行空间 销毁
function fn () { let obj = { a: 1, b: 2 } return obj } let o1 = fn()
-
认识闭包
-
构成条件:
-
- 需要一个不会被销毁的函数执行空间
-
- 需要 直接 或 间接 的返回一个函数
-
- 内部函数使用着 外部函数的私有变量
-
-
闭包的好处: 延长变量的作用域(使用时间)
- 函数外部可以使用函数内部的变量了
-
闭包的弊端: 因为闭包的存在就一定代表一个函数执行空间不会被销毁, 如果大量使用闭包, 会导致内存空间占据严重
function outer() { let a = 100 let str = 'abc' 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 = 'abc' 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) // 'abc' res.setA(999) // console.log(res.getA()) // 999 // 2. 调用了外部函数 outer, 这个函数内部会返回一个对象 obj, 然后我将它存储到了变量res2 中 let res2 = outer() console.log(res2.getA()) // 100
沙箱模式的语法糖
-
语法糖: 再不影响功能的情况下, 对我们的语法做一点简化操作
-
通过 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.a) res.a = 999 console.log(res.a)