一、闭包
- 官方解释:一个函数和对其周围状态的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包。闭包就函数内部的函数,可以在内部函数访问外部函数的作用域;在js中,每当创建一个函数,闭包就会在函数创建的同时创建出来。
通俗的来说:JavaScript中所有的function都是一个闭包。不过一般来说,嵌套的function所产生的闭包更为强大,也是大部分时候我们所谓的“闭包”。 - 闭包的作用
a.可以读取函数内部的变量
b.让这些变量的值始终保持在内存中
通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止; - 闭包注意点
内存泄露
由于闭包会使函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,负责会造成网页的性能问题,在ie中可能会导致内存泄露(内存不用,但未释放)
4. 闭包的示例;
a 只有一个方法的对象
b 如果某些数据,只希望某些函数操作使用
c用闭包模拟私有方法
d循环里使用闭包
e局部变量的累加
5. 函数可以访问自己内部的变量(局部变量)
函数可以访问外部的变量
全局变量是函数外部变量的一种体现
闭包中,父函数定义的变量之于闭包就是外部变量
6.作用域:变量起作用的范围,有全局变量和局部变量。
作用域链是指:当js编译器在寻找变量时,先在最近的作用域(花括号)里找,如果找不到,则抄上一级作用域(花括号)里找,依次类推,直到找到或者找不到为止。这就是作用域链。
二、函数是个对象
1.函数的三种定义方式
1、函数声明
function t(aa){
alert(“亲”+aa)
}
2、函数表达式(匿名函数)
var t=function(aa){
alert(“亲”+aa)
}
3、对象的方式:
var t=new function(“形参”,alret(‘输出’)));
2、函数是功能完整的类(对象)
函数就是一个对象,
var name=new Function(arg1,…agrn,function_body)
每个 arg 都是一个参数,最后一个参数是函数主体(要执行的代码)。这些参数必须是字符串。
3、函数的内置对象
内置对象:就是自动产生,随着函数的调用而存在的对象,函数里的内置对象有:arguments和this。箭头函数中没有arguments和this。
1)、this
this对象引用的是函数赖以执行的环境的对象,即函数属于哪个对象的,this就是哪个对象, 有可能出现this代表不同的对象,因为,一个函数有可能被赋给不同的对象。如果函数哪个对象都不属于,就属于全局的window对象
2)、arguments
arguments是个伪数组(不是用array new出来的,)但是可以用下标方式访问元素,也可以用length属性。但是不能使用数组的官方方法。agruments对象保存函数的所有参数
arguments对象有一个名叫callee的属性。callee属性是个指针(地址,引用类型)指向了arguments对象所在的函数。collee属性有个好处。
function factorial(num){
if(num<=1){
return 1;
} else{
//return numfactorial(num-1);
return numarguments.callee(num-1);
}
}
如果函数名需要修改,函数体里就不用修改函数名了。
4、函数的属性和方法
1)、prototype属性:每个函数都有一个属性是prototype(原型),这个属性是个指针(地址,引用类型),指向一个对象,该对象的用途是包含所有实例(对象)共享的属性和方法。所有通过同一个构造函数创建的实例对象,都会共享一个prototype。
从上图可以看出:
1.每个构造函数都有个prototype属性(引用类型 )。
2. prototype指的是内存空间,有个constructor。constructor指向了构造函数本身。
3. 每个对象内存中有proto,它和构造函数的prototype属性指向了同一块内存空间。
原型的属性和实例的属性:
实例属性:用new调用构造函数创建出来的对象叫做实例。构造函数里写的属性就是实例属性,每个实例属性的内存空间是独立的,不会共享。
原型属性:写在prototype后面的叫做原型属性,是所有实例共享的内存空间。
5、call和apply方法
每个函数都有两个非继承而来的方法apply()和call()。这两个方法都是用来调用函数的,实际上等于设置函数体内的this对象的值。调用函数实际上就是调用该函数call内部的方法。
6. call的作用:q是调用函数本身的 b 调用函数的时候会改变函数内部的this指向
1)、call方法的第一个参数是函数内部的this指向。
2)、call方法的从第二个朝后的参数是函数本身的参数。
3)、你可以认为,把对象和函数解耦了
区别:
apply()方法有两个参数,分别是运行函数的作用域(this指向),另一个是参数数组(可以是array也可以是arguments)。
call()方法的第一个参数 和apply()的第一个参数一样,其他参数就是调用函数参数(相当于把,apply第二个参数的每个元素单列出来)
4)、ES5_bind和this关键字
bind方法也是函数的方法。bind方法可以改变this的指向。
bind()方法会创建一个新函数,称为**绑定函数(家).**当调用这个绑定函数时,绑定函数会以创建它时传入bind()方法的第一个参数作为 this。
bind,call和apply的区别
1、相同点:
三个函数都会改变this的指向(调用这三个函数的函数内部的this)
2、不同点:
1)、bind会产生新的函数,(把对象和函数绑定死后,产生新的函数),而不调用函数本身。
2)、call和apply不会产生新的函数,只是在调用时,绑定一下而已。会调用函数本身
3)、call和apply的区别,第一个参数都是要绑定的this,apply第二个参数是数组(是函数的所有参数),call把apply的第二个参数单列出来。
三、继承
所谓继承:
就是子类自动拥有父类的属性和方法, 继承可以提高代码的复用性。
JS里的继承主要依靠是的原型链。让原型属性(每一个构造函数都有一个原型对象prototype),等于另一个类型的实例,即实现了继承;另外一个类型的原型再指向第三个类型的实例,以此类推,也就形成了一个原型链
2、Prototype实现继承
原型继承
让子对象(ES6中的子类)的原型指向父对象(类)的实例(对象),就完成 了继承
原型链:
让子类的原型指向父类的对象,父类的原型指向爷爷类的对象,依次类推,就形成了原型链
原型链继承的注意点:
- 先定义原型继承关系,再添加子类的特有方法或属性(原型的属性,即共享的属性和方法要在原型继承关系确立后,再定义)。
- 利用原型链继承,给子类添加特有的原型方法时,不可以重写prototype(不要再给prototype属性赋JSON类型的值就行)
原型链继承的缺点:
1. 创建子类型的实例时,没法传参给被继承类型。
2. 被继承的类型(父类)里包括引用类型的属性的时候,它会被所有实例共享其值
3、apply()和call()实现继承
apply()和call()是调用函数的,当然也能调用构造函数,也就能改变构造函数内部的this。
这种方式,其实就是在子类型中借用父类的构造函数来生成自己的实例对象的属性。它可以向父类型的构造函数传递参数来初始化自己的实例属性。
单独使用这种借用构造函数的模式,如果所有要继承的属性和方法都在父类型的构造函数里定义,特别是实例共享的属性和方法也写在构造函数里,那么这样会浪费内存。所以,很少很少单独使用
4、组合继承
结合前两种方式:原型链式继承和Call()/Apply()方式继承,我们就能解决前面提出的那些问题。
1)、利用原型链继承(父类prototype上的)共有的属性和方法,
2)、利用Call/Apply来继承父类构造函数里的属性(和方法)。
5、ES6的继承
ES6的继承只是语法糖。
关键字:extends,super
1)、extends:
extends是继承的关键字
class 子类名 extends 父类名{
}
2)、super:
super表示父类(的构造函数),在子类的构造函数里,必须要调用父类的构造函数,而且,要写在子类构造函数里的第一句话
class 子类名 extends 父类名{
constructor(){
super();//调用父类的构造函数
}
}
继承的四种方式
构造函数继承:
构造函数继承:利用call、apply实现
构造函数继承只能 继承父类中的属性,不能继承原型对象上的属性和方法
function Fu(name,age){
this.name=name;
this.age=age;
}
function Son(name,age){
Fu.apply(this,[name,age]);
}
let s1 = new Son('张三',12)
console.log(s1);
原型链继承:
function Fu(name,age){
this.name=name;
this.age=age;
}
Fu.prototype.sex=function(){
console.log('nan');
}
function Son(name,age,score){
this.name=name;
this.age=age;
this.score=score;
}
Son.prototype=new Fu('张三',56);
Son.prototype.hh=function(){
console.log('dd');
}
let s=new Son('张sss',18,180);
s.sex();
s.hh();
let f=new Fu('张三',56)
f.sex();
组合继承:
function Fu(name,age){
this.name=name;
this.age=age;
}
Fu.prototype.sex=function(){
console.log('nan');
}
function Son(name,age,score){
Fu.apply(this,[name,age]);
}
Son.prototype=new Fu('张三',56);
Son.prototype.constructor=Son;
Son.prototype.hh=function(){
console.log('dd');
}
let s=new Son('张sss',18,180);
s.sex();
s.hh();
ES6中的继承:
class Fu{
constructor(name,age){
this.name=name;
this.age=age;
}
sayHi(){
console.log('哈哈');
}
}
class Son extends Fu{
constructor(name,age,score){
super(name,age);
this.score=score;
}
}
let s=new Son('张三',18)
console.log(s);
s.sayHi();