const NEW = function () {
let fn = Array.prototype.shift.call(arguments);
let obj = Object.create(fn.prototype);
let o = fn.apply(obj, arguments);
return typeof o === 'object' ? o : obj;
}
多态
从一个父类继承出来的子类有不同的形态:
function Person() {
this.food = 'meat';
}
function Chinese() {
this.skill = 'Kongfu';
}
function Japanese() {
this.skill = 'Ninjutsu';
}
function American() {
this.skill = 'Boxing';
}
Chinese.prototype = Japan.prototype = American.prototype = new Person();
var C = new Chinese();
var J = new Japanese();
var A = new American();
console.log(C.food, C.skill);
console.log(J.food, J.skill);
console.log(A.food, A.skill);
JavaScript面向对象小结
面向对象的语言有三大特征:
封装
、继承
、多态
。所谓封装,就是将客观事物封装成抽象的类,并且类可以把自己的数据和方法让指定的类或对象操作,比如有些属性和方法是私有的,不能被外界访问。通过封装,可以对对象内部数据提供不同级别的保护。
所谓继承,就是可以让某个对象获得另外一个对象的属性或方法。其概念的实现方式可分为两类:实现继承和接口继承:实现继承是指直接使用基类的属性和方法而无需额外编码能力;接口继承是指仅使用属性和方法的名称,但子类必须提供实现的能力。
所谓多态,就是指一个类实例的相同方法在不同情形有不同的表现形式,使得具有不同内部结构的对象可以共享相同的外部接口。
在JavaScript,同样支持以上的的三个特性。但是,由于在ES5及之前的标准中没有引入
class
关键字,所以其面向对象的功能需要借助原型链来实现。封装
通过构造函数封装:
构造函数和普通函数别无二致,它用来初始化对象。在命名规范中,它的首字母需要大写。
通过构造函数添加属性和方法,实际上就是通过
this
添加属性和方法,因为函数内的this
总是指向当前对象。在实例化对象时,都会复制一份属性和方法。如果构造函数内的属性和方法不是通过
this
添加的,那么该属性和方法就是私有属性和私有方法,其实例无法直接访问。而通过this
添加的方法又称为特权方法,通过它可以让实例访问私有属性和私有方法。继承
ES5继承
虽然没有
class
,但是由于JavaScript的函数作用域(函数外部无法当问函数内部的变量),我们可以借此模拟class
,将属性和方法都保存在一个函数中。原型链继承
将方法绑定在原型链中,该方法就是对象的公有方法,可以被子对象引用。
使用原型链继承,每次创建一个子类实例,都需要重复地执行
new
操作。并且,由于原型链继承里面使用的都是同一个内存的值,假如修改其中一个子类实例继承的属性,将会影响到其他的子类实例:构造函数继承
通过构造函数继承,可以避免实例对共享数据的影响,同时可以在子类中向父类传参:
组合继承
由上两种继承方式可知,原型链实现的继承都是复用同一个属性和方法,构造函数实现的继承都是独立的属性和方法。我们可以同时结合这两种继承方式,在原型上定义方法实现函数复用,通过构造函数有使得每个实例都有自己的属性:
原型式继承
这种方式是模拟
Object.create
,将传入的对象作为创建的对象的原型:和原型链继承方式一样,不同实例也是使用同一内存的数据,可能会造成污染:
在上述代码中,修改了实例
person1
的age
属性,但是不会影响到person2
的age
属性,这是因为person1.age = 20
的操作并未改变原型上的age
。在查找对象上的属性时,总是优先查找实例对象,没有找到的情况下再查找原型对象上的属性,实例对象和原型对象上如果有同名属性,优先取实例对象上的值。寄生式继承
创建一个仅用于封装继承的函数,其内部辅以属性和方法,最终返回对象:
寄生组合继承
稍微封装一下:
ES6继承
在ES6中,引入了
class
和extends
概念,用来实现对象继承:子类必须要在
constructor
中调用super
,否则创建实例时会报错,因为子类没有自己的this
对象,而是继承自父类的。在调用super
后,子类才能使用this
。这样就体现出和ES5的不同:ES6的静态方法和静态属性
在ES6中,假如我们用
static
来修饰对象内的方法,那么该方法就是静态方法,它只能通过直接调用类来使用,而不能被实例调用,同时它是可以被子类继承使用的:甚至,还可以通过
super
来调用父类的静态方法:静态属性是指类本身的属性,而不是定义在实例对象(
this
)上的属性,所以我们可以这样:当然,在ES6中,可以通过
static
来修饰以实现静态属性:ES6本身没有私有方法和私有属性的具体实现标准,我们可以通过某种方式来达到想要的效果,详情可以参考: ECMAScript6入门 和 ES6 系列之私有变量的实现
当我们new了一个实例,前后发生了什么?
比如
var child = new Child()
:child.__proto__ = Child.prototype
,将新创建的对象的__proto__
指向构造函数的prototype
this
指向新创建的对象模拟
new
:多态
从一个父类继承出来的子类有不同的形态: