1 面向对象的js
1.1 动态类型语言和鸭子类型
- 编程语言按数据类型可分成静态类型和动态类型语言,其中js属于动态类型语言,对一个变量赋值时,不需要考虑其类型。
- 鸭子类型:
- 通俗理解,只要走起路来像鸭子,叫起来也像鸭子,那么它就是鸭子,如果一只鸡具有鸭子的行为,那它就可以被代替为鸭子。
- 鸭子类型指导我们只关注对象的行为,而不关注对象本身。
1.2 多态
- 定义:同一操作作用于不同的对象,可以产生不同的解释和执行结果。
- 举例说明:在动物类中,假如有一只鸡和一只鸭,对它们执行“叫”的命令,则鸡鸭分别会发出不同的叫声,而**“叫”的命令相当于操作,叫声相当于执行结果**
- 作用:通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句。
1.3 封装
- 理解:封装的目的是将信息隐藏,封装包括封装数据、封装实现、封装类型和封装变化。
- 封装数据:在许多语言的对象系统中,封装数据是由语法解析实现的,这些语言提供了private、public、protected等关键字来提供不同的访问权限。而JS中,只能依赖变量的作用域来实现封装特性。如ES6中的块级作用域、函数作用域。
- 封装实现:封装使得对象内部的变化对其他对象而言是透明的,对象对它自己的行为负责。封装使得对象之间的耦合变松散。
- 封装类型:封装类型是静态类型语言中分一种重要的封装方式,通过抽象类和接口来进行的。而JS中,没有这方面的能力,也没有必要做得更多。
- 封装变化:可以把系统中稳定不变的部分和容易变化的部分隔离开来,在系统演变的过程,只需要替换那些容易变化的部分。在最大程度上,保证程序的稳定性和可扩展性。
1.4 原型模式和基于原型继承的JS对象系统
- 原型模式:不用关心对象的具体类型,而是找到一个对象,并通过克隆来创建一个一模一样的对象。其实现关键在于语言本身是否提供了clone方法,es5中提供Object.create方法可以用来克隆对象。
- JS的原型编程基本规则以及JS是如何在这些规则的基础上构建它的对象系统的
- 所有的数据都是对象:JS在设计时,引入了基本类型和对象类型。基本类型中number,boolean,string数据并不是对象,那在设计过程中需要通过“包装类”的方式变成对象类型来处理。
- 虽然我们不能说在JS中所有的数据都是对象,但是绝大部分数据都是,同时在JS中也一定会有一个根对象存在,即Object.prototype对象。
var obj1 = new Object();
var obj2 = {};
console.log( Object.getPrototypeOf( obj1 ) === Object.prototype ); // 输出:true
console.log( Object.getPrototypeOf( obj2 ) === Object.prototype ); // 输出:true
- 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它
- 对象会记住它的原型
- 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型
2 this、call和apply
2.1 this的指向
- 作为对象的方法调用
- 当函数作为对象的方法被调用时,this指向该对象。
var obj = {
a: 1,
getA: function(){
console.log(this === obj); // true
console.log(this.a); // 1
}
};
obj.getA();
- 作为普通函数调用
- 当函数不作为对象的属性被调用时,this指向全局对象,在浏览器的JS中,全局对象是window对象。
var name = 'globalName';
var getName = function(){
return this.name;
};
console.log( getName() ); // globalName
// 或者
var name = 'globalName';
var myObject = {
name: 'sven',
getName: function(){
return this.name;
}
};
var getName = myObject.getName;
console.log( getName() ); // globalName 函数不作为对象的属性调用
console.log(myObject.getName()); // sven 函数作为对象的属性调用
- 构造器调用
- 当用new 运算符调用函数时,该函数返回一个对象,而通常情况下,构造器里的this就指向返回的这个对象。
var MyClass = function(){
this.name = 'sven';
};
var obj = new MyClass();
console.log(obj); // MyClass {name: 'sven'}
console.log(obj.name); // sven
// 如果构造器显式的返回一个object类型的对象,那么此次运算结果最终返回该对象
var MyClass = function(){
this.name = 'sven';
return { // 显式地返回一个对象
name: 'anne'
}
// 若返回的不是objecr类型的对象,如 ['a'], 则obj打印为['a'], obj.name打印为undefined
};
var obj = new MyClass();
console.log(obj); // {name: 'anne'}
console.log(obj.name); // anne
// 如果不显式地返回任何数据,或者返回一个非对象类型的数据,则不会造成上述问题:
var MyClass = function(){
this.name = 'sven';
return 'a'
};
var obj = new MyClass();
console.log(obj); // MyClass {name: 'sven'}
console.log(obj.name); // anne
- call或apply调用
- 它的出现可以动态的改变传入函数的this
var name = "globalName";
var obj1 = {
name: 'sven',
getName: function(){
return this.name;
}
};
var obj2 = {
name: 'anne'
};
console.log( obj1.getName() ); // sven
console.log( obj1.getName.call( obj2 ) ); // anne
var obj3 = obj1.getName;
console.log(obj3()); // globalName
var obj3 = obj1.getName;
console.log(obj3.call(obj2)); // anne
2.2 call和apply
- 区别:
- apply:接受两个参数,第一个参数指定函数体内this对象的指向,第二个数组为一个带下标的集合,可以是数组,也可以是类数组。
- call:接受两个参数,第一个参数指定函数体内this对象的指向,从第二个参数开始往后,每个参数被依次传入函数。
- 当使用call或者apply时,如果传入的第一个参数为null,则this会指向默认的宿主对象,在浏览器中则是window。但在严格模式下,函数体内的this还是为null。
- 用途:
- 改变this指向
- 借用其它对象的方法
3 闭包和高阶函数
3.1 闭包
- 变量的作用域:当在函数中搜索变量的时候,如果该函数内并没有声明这个变量,那么此次搜索的过程会随着代码的执行环境创建的作用域网外层逐层搜索,一直搜索到全局对象为止。变量的搜索是从内到外的。
- 变量的生存周期:对于全局变量而言,生存周期是永久的,除非主动销毁这个全局变量,而var关键字声明的局部变量,当退出函数时,这些局部变量失去了它们的价值,会随着函数调用的结束而被销毁。
- 闭包的作用
- 封装变量:帮助把一些不需求暴露都在全局的变量封装成“私有变量”
-延续局部变量的寿命
3.2 高阶函数
- 满足条件:
- 函数可以作为参数被传递: 如回调函数,Array.prototype.sort。
- 函数可以作为返回值输出:如判断数据的类型,getSingle
4 单例模式
- 定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
4.1 实现单例模式
var Singleton = function( name ){
this.name = name;
this.instance = null;
};
Singleton.prototype.getName = function(){
console.log(this);
console.log(this.name);
};
Singleton.getInstance = function( name ){
console.log(this);
console.log(this.instance);
if ( !this.instance ){
this.instance = new Singleton( name );
}
return this.instance;
};
var a = Singleton.getInstance( 'sven1' );
var b = Singleton.getInstance( 'sven2' );
console.log(a);
console.log(b);
console.log(a.name);
分析:首先这里的函数都是以函数表达式的形式定义,因此不存在声明提前,当执行
var a = Singleton.getInstance( 'sven1' );
时,在Singleton.getInstance
方法中,this指向该函数