继承的原理
如果 子类没有某一种属性或者方法 我们可以 通过继承的方式 让子类继承到父类的方法和属性
- 优点: 实现了继承 可以使用自身没有的属性和方法
- 缺点:继承了父类的方法和属性,但不在自己身上,失去了自己的原型
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}
// 如果我们想要 s 这个对象里面有一个属性, 可以使用 sayName 这个方法 我们就可以通过继承的方式 让Stu这个类,继承上Person上的方法和属性
原型继承
此时第一个函数时父类, 第二个函数是子类 子类可以继承父类内部的属性和方法 在继承中 父类和子类不是绝对的 第一个函数也可以为字类 第二个函数也可以为父类
- 去 对象内部查找 name 属性, 然后发现对象内部没有这个属性
- 去这个对象内部的 proto 上查找, 对象的__proto__ 指向了自己构造函数的 原型 也就是 指向了 Stu.prototype
- 因为我们手动修改了 Stu.prototype, 给他赋值为了 Person 构造函数的实例化对象
- 也就是说 我们 Stu.prototype 就指向了 Person的实例化对象
- Person 的实例化对象 内部 有一个属性叫做 name, 此时找到并返回
// 第一个函数
function Person(name) {
this.name = name
}
Person.prototype.sayName = () => {
console.log('name')
}
// 2. 第二函数
function Stu(age) {
this.age = age;
}
Stu.prototype = new Person('QF001');
const s = new Stu(18);
console.log(s);
console.log(s.age); // 18
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)
-
通过 new 关键字调用 Stu 这个构造函数
-
new 关键会在这个函数内部创建一个空对象, 并且 会把这个函数内部的 this 指定刚才创建的空对象
-
我们现在开始正常执行函数代码
3.1 给这个对象 添加一个 age 属性, 并且给他赋值 为 参数 age 参数 age === 18
3.2 调用 Perosn 这个 函数, 并通过 call 方法 改变这个函数内部的 this 指向, this 指向了 刚才 new 关键字创建的 对象,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 循环遍历 父类Person 实例化对象上的所有属性以及方法 将它拷贝到字类上
function Person(name){
this.name = name;
}
Person.prototype.setName = () => {
console.log('name');
}
function Stu(age){
this.age = age;
const p = new Person('QW111');
for(let key in p){
// 通过for in 循环遍历到父类实例化对象上的所有属性,拷贝到字类上
Stu.prototype[key] =p[key];
}
}
const s = new Stu(18);
console.log(s);
// 使用 Person 上的name属性
console.log(s.name); // QW111
// Stu 使用到 Person原型上的方法
s.setName(); // name
ES6 类的继承
Class 可以通过extends关键字实现继承,让子类继承父类的属性和方法。extends 的写法比 ES5 的原型链继承,要清晰和方便很多。
class Person{ // 父类 => Person
}
class Stu extends Person{ // 子类 => Stu
}
// 父类是 Person 字类是 Stu , Stu 通过关键字 extends继承了父类 Person 上的所有属性和方法
- 在书写 子类的时候 语法: class 子类类名 extends 父类类名
- 在书写 子类 constructor 需要在内部书写 super()
- 注意:
- 两个写法 必须同时存在, 才能完成继承
- super() 必须在 constructor 在开始位置
- 类的继承, 除了能够继承 calss 类, 还能够继承 ES5 的构造函数
class Person{
constructor(name){
this.name = name;
}
setName(){
console.log('name');
}
}
class Stu extends Person{
constructor(age){
super('QF111');
this.age = age;
}
}
const s = new Stu(19);
console.log(s);
console.log(s.name); // 父类继承来的属性name
s.setName(); // 父类继承来的方法 setName
深浅拷贝
-
一定是 引用数据类型
-
对象, 数组, 函数(基本只有对象和数组)
-
赋值:
- 只要是引用数据类型, 那么在赋值的时候, 就是引用地址的传递
-
浅拷贝
- 遍历对象拿到对象的每一个key与value
- 然后赋值给另外一个 对象
- 如果所有的 value 都是基本数据类型, 那么浅拷贝完成之后, 修改新对象不会影响到老对象
- 如果 value 有引用数据类型(出现了多层数据结构), 那么浅拷贝只能拷走第一层数据结构, 多层的没有办法处理
let obj = {
a:1,
b:2,
c:3,
d:{
d1:011,
d2:02,
d3:03
}
}
let obj1 = {}; // 定义一个空对象 用于存储拷贝后的数据
// 通过for..in 遍历拿到对象 obj 的所有 key 值
for(let key in obj){
// 将 对象 obj 中的所有 key 值 赋值给 空对象 obj1
obj1[key] = obj[key];
}
console.log(obj1);
console.log(obj);
console.log(obj === obj1); // false
obj1.b = 999;
console.log(obj1);
console.log(obj);
console.log(obj1.d.d1);
// 修改拷贝后的数据 原数据不会发生改变
- JS 提供的浅拷贝方法
- 语法:Object.assign(新对象, 原始对象);
- 将 原始对象 中所有的 key 全部浅拷贝到 新对象中, 然后返回一个 对象
let obj = {
a:1,
b:2,
c:3,
d:{
d1:011,
d2:02,
d3:03
}
}
let obj1 = Object.assign({},obj)
console.log(obj1);
console.log(obj);
深拷贝
不管数据结构有多少层数据结构,都会把它复制出来,其一模一样,但复制出来的数据和原数据毫不相干
let obj = {
a:1,
b:2,
c:3,
d:{
d1:11,
d2:22,
d3:33
}
}
let obj1 = {}; // 定义一个空对象 用于存储拷贝后的对象
function fun1(target,origin){
// 将origin 中的数据 拷贝到 target中
for(let key in origin){
// 对象 就让它
if(origin[key].constructor === Object){
target[key] = {};
fun1(target[key],origin[key]);
// 数组
}else if (origin[key].constructor === Array) {
target[key] = [];
deepClone(target[key], origin[key])
// 数值或字符串
} else {
target[key] = origin[key]
}
}
}
fun1(obj1,obj);
console.log(obj1);
console.log(obj);
console.log(obj1 === obj); // false
JS中 深拷贝的方法
// 方法
let obj2 = JSON.parse(JSON.stringify(obj));
obj2.d.d2 = 666;
console.log('拷贝后的对象:',obj2);
console.log('原对象:',obj);
闭包
-
闭包:就是能够读取外层函数内部变量的函数。
-
闭包需要满足三个条件:
- 需要一个不会被销毁的函数执行空间
- 需要 直接 或 间接 的返回一个函数
- 内部函数使用着 外部函数的私有变
-
优点: 可以重复使用变量 (延长变量的作用域 [使用时间] ),并且不会造成变量污染 (函数外部可以使用函数内部的变量) 。
-
缺点: 会引起内存泄漏,因为闭包的存在就一定代表一个函数执行空间不会被销毁, 如果大量使用闭包, 会导致内存空间占据严重
-
使用闭包的注意点:
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会 造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的 局部变量全部删除。
- 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象 (object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性 (private value),这时一定要小心,不要随便改变父函数内部变量的值
function outer() {
let a = 100;
let str = 'QF111';
let obj = {
a: 1,
b: 2,
c: 3
}
function inner() {
return a; // 返回外部变量
}
return inner; // 返回内部函数
}
let res = outer();
console.log(res); // inner函数
let newRws = res();
console.log(newRws); // 100
沙箱模式
沙箱模式 就是 利用 间接 返回一个函数, 然后去拿到 外部函数内的私有变量
function outer(){
let a = 100;
let str = 'QF111';
const obj = {
getA:function(){
return a;
},
getStr:function(){
return str;
},
setA(val){
a = val;
}
}
return obj;
}
let res = outer();
console.log(res); // 返回对象
let res1 = res.getA(); // 100
console.log(res1);
let res2 = res.getStr();
console.log(res2); // QF111
res.setA(999);
console.log(res.getA()); // 999
沙箱模式的优化
通过 getter 和setter 来简化代码的书写
function outer(){
let a = 100;
let b = 300;
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);
console.log(res.a); // 100
res.a = 999;
console.log(res.a); // 999