一:闭包
在一些使用场景中,我们不想使用全局变量,但是又不想每次使用后改变其值,这个时候该怎么做呢。高级一点说”一个变量既能重复使用,又不会污染全局“这时候
闭包
就产生了
闭包的重要理解:闭包可以在全局函数里面操作另一个作用域的局部变量
形成闭包的步骤
一:外层函数嵌套内层函数
二:内层函数访问外层函数的变量
三:外层函数的返回值为内层函数(注:不是返回内层函数的结果而是内层函数)
闭包的实现原理
这里引用了
熊大林老师的关于闭包深入理解的文章
2、外层函数调用时
3、外层函数调用后
4、内层函数调用时
5、内层函数调用后
闭包分析
在形成闭包后,外层函数的活动对象(函数在调用的时候被创建的对象)与内层函数的执行上下文,内层函数调用形成的执行栈形成一个稳定的三角形,且不能释放,所以在闭包创建后,在不调用的使用的时候,会造成内存浪费
换句话说,
只要涉及到函数嵌套的时候,都会有闭包的存在
二:对象的继承
在js中,由于类是基于原型链和构造函数来实现的,所以js对象(类)的继承也有不同的方式
一:原型链继承
原理:定义一个父类以及子类,将子类的原型对象,指向父类的实例对象,这样便可以访问到父类所有的属性与方法了
//定义一个动物类
function Animal(name){
this.name = name
}
Animal.prototype.say(){
console.log(`My name is ${this.name}`)
}
//定义一个cat类,因为下面会继承Animal类,所以不需要写什么东西
function Cat(){
}
//将Cat的原型对象执行Animal的实例对象
Cat.prototype = new Animal('jerry') //原型链继承的重点代码
//这个时候new Cat对象便能访问Animal的属性方法了
var jerry = new Cat()
jerry.say()
原型链继承的原理图
原型链继承的优缺点
有点:
非常纯粹的继承关系,实例是子类的实例,也是父类的实例
父类新增原型方法/原型属性,子类都能访问到
简单,易于实现
缺点:
要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
无法实现多继承
来自原型对象的引用属性是所有实例共享的
创建子类实例时,无法向构造函数传参
二:构造函数继承
原理:还是一个定义父级类,子类。在子类的构造函数中,调用父类(这里将父类看出普通的函数,直接调用),不过需要注意的是,父类里面的this指向问题
//定义一个动物类
function Animal(name){
this.name = name
}
//定义一个Cat类,并指定形成name
function Cat(name){
//将动物类看出一个普通函数,在这里调用,并传入形参name
Animal.call(this,name) //注意由于在动物类中,涉及到this指向,如果不更改的话,this将指向window,无法指向Cat的实例对象。利用apply或call方法修改this
}
//new一个Cat实例对象,这里可以传入name参数了
var jerry = new Cat('jerry')
构造函数继承的优缺点
优点:
解决了原型链继承中,子类实例共享父类引用属性的问题创建子类实例时
可以向父类传递参数可以实现多继承(call多个父类对象)
缺点:
实例并不是父类的实例,只是子类的实例
只能继承父类的实例属性和方法,不能继承原型属性/方法
无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
三:组合继承(原型链继承与构造函数继承相结合)
原理:原型链继承与构造函数继承相结合,弥补各自的不足
//定义一个动物类
function Animal(name){
this.name = name
}
//创建原型方法
Animal.prototype.say(){
console.log(`My name is ${this.name}`)
}
//定义一个Cat类,并指定形成name
function Cat(name){
//将动物类看出一个普通函数,在这里调用,并传入形参name
Animal.call(this,name) //注意由于在动物类中,涉及到this指向,如果不更改的话,this将指向window,无法指向Cat的实例对象。利用apply或call方法修改this
}
//将Cat的原型对象指向Animal的实例对象
Cat.prototype = new Animal();
//由于原型链继承没有构造函数属性,所以为了严谨,我们自己添加
Cat.prototype.constructor = Cat;//当然这里也可以不写,只是为了更加严谨
//最后使用原型链的方法,看看是否继承成功
console.log(cat instanceof Animal); // true
var cat = new Cat();
console.log(cat instanceof Cat); // true
组合继承的优缺点
优点:
弥补了原型链继承与构造函数继承的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
既是子类的实例,也是父类的实例
不存在引用属性共享问题
可传参
函数可复用
缺点:
调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
四:寄生组合继承(组合继承的升级版)
原理:在原型对象赋值的时候,通过一个子调用函数来实现,调用结束后,及时销毁,就不会存在生成了两份实例(子类实例将子类原型上的那份屏蔽了)这个问题了
function Cat(name){
Animal.call(this);
this.name = name;
}
(function(){
// 创建一个没有实例方法的类
var Super = function(){};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Super();
})();
// Test Code
var cat = new Cat();
console.log(cat.name);
cat.say();
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true
寄生组合继承的优缺点
优点:堪称完美
缺点:实现较为复杂
五:拷贝继承(不推荐,做了解)
原理:遍历父类的属性方法赋值给自己
function Cat(name){
var animal = new Animal(name);
for(var key in animal){
Cat.prototype[key] = animal[key];
}
}
// Test Code
var cat = new Cat("Tom");
console.log(cat.name);
cat.say();
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
拷贝继承的优缺点
优点:
支持多继承
缺点:
效率较低,内存占用高(因为要拷贝父类的属性)
无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
六:语法糖继承(ES6继承)
使用最多,最方便的继承方法
class Animal{
constructor(name){
this.name = name;
}
say(){
alert("My name is "+this.name);
}
eat(food){
alert(this.name+" is eating "+food);
}
}
//使用extends关键字,自动继承原型方法,属性继承,需要使用super关键字
class Cat extends Animal{
constructor(name){
super(name);//这里传入构造函数的参数
// 注意:如果子类的构造函数里还有一些别的代码一定要放在super之后
}
}
var tom = new Cat("Tom");
tom.say();
tom.eat("apple");
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true