es5和es6类的背景
js语言不向其它面向对象语言,在es6以前没有类这种类型,直到es6的诞生,js才开始真正引入类似于其他面向对象编程语言的类-class。但因为js语言非常灵活,在es6之前已经有各种方法模拟实现类结构,其他语言中类的功能基本上已经实现。
使用函数封装一个类写一个类
下面是使用js写的一个示例:
var Student = function (id, name) {
let secret = '1234'; // 私有属性
let person = function () { // 私有方法
console.log('person function!');
};
this.id = id; // 公有属性
this.name = name;
this.public = function () { // 公有方法
console.log('public function!');
};
};
Student.prototype.add = function () { // 共有方法
cosnole.log('prototype function!');
};
Student.prototype.other = 123; // 共有属性
Student.room = function () { // 类静态公有方法
console.log('room 343');
};
Student.phone = '13567859874'; // 类静态公有属性
以上代码封装了一个简单的类,包含了私有属性和方法、公有属性和方法、共有属性和方法以及类静态公有属性和方法。由于js的函数级作用域,声明在函数内部的变量以及方法在外界是访问不到的,因此私有属性和方法就是这样实现的。然而, 在函数内部通过this创建的属性和方法,在类创建对象时,每个对象自身都拥有一份并且可以在外部访问到,因此通过给this赋值可以实现共有属性和方法。通过new关键字(通过new关键字创建的对象实质是对新对象this的不断赋值)创建新对象时,由于类外面的通过点语法添加的属性和方法没有执行到,所以新创建的对象中无法获取他们,但是可以通过类来使用,类的静态共有属性和方法就是建立在此之上的。通过prototype创建的属性和方法在类实例的对象中是可以通过this访问到的,所以prototype对象的属性和方法也称为共有属性和方法。
js是一种基于原型prototype的语言,所以每创建一个对象是,它都有一个原型prototype用于指向其继承的属性和方法。这样通过prototype继承的方法并不是对象自身的,所以在使用这些方法时,需要通过prototype一级一级的查询。当我们通过new关键创建新对象时this指向的属性和方法都会得到相应的创建,但是通过prototype继承的属性和方法是每个对象通过prototype访问到的,所以我们通过创建的新对象访问这些属性和方法(prototype上的属性和方法)不会再次创建。
在类的构造函数外面通过点语法定义的属性以及方法是不会添加到新创建的对象上去的,因此要访问创建的room或者phone需要通过Student类使用,不能通过实例调用。
验证结果
创建两个Student实例s1, s2;包含内容如下:
结果和我们猜想的一样,使用this定义的属性和方法每个实例都有一份,但是定义在类prototype对象上的属性和方法以及直接使用点语法定义在Student类上面的属性以及方法都未在实例中创建。但是是可以访问的,验证结果如下:
可以从运行结果中看出prototype上的方法和属性其实是共用的,也就是在实例化对象的时候不会单独创建一份,因此在对数组进行操作的时候要非常小心(可能会出现意外结果,可以自己探索一下)。
私有属性以及类静态公有方法和方法通过实例访问的结果如下:
和我们的预期的结果完全一致。但是静态公有属性和方法是可以通过类进行访问的,结果如下:
其实还可以实现类的静态私有变量和方法,具体代码和上面类似,只是将上面的函数用闭包包裹返回,在闭包里面定义类的静态属性和方法(函数作用域)。
继承
由于js语言极其灵活,因此实现继承的方式很多,这里只记录几种比较常用的:类式继承、构造函数继承和组合继承。像寄生式继承、原型式继承(类似类式继承)以及他们的组合式继承这里将不会记录。
类式继承
通过以下示例来了解类式继承:
function Parent () { // 声明父类
this.father = 'change';
this.problem = ['123', '456'];
};
Parent.prototype.getFather = function () { // 为父类添加共有方法
return this.father;
};
function Son () { // 声明子类
return this.son = 'son';
};
Son.prototype = new Parent(); // 继承父类
Son.prototype.getSon = function () { // 为子类添加共有方法
return this.son;
};
实现类继承的核心Son.prototype = new Parent();,至于为什么这样实现继承?首先了解一下类原型对象的作用是为类的原型添加共有方法,但是类不能直接访问这些属性和方法,必须通过原型prototype来访问。而实例化一个父类复制了父类的构造函数内的属性和方法并且将原型_proto_指向了父类的原型对象,这样就拥有了负的原型对象对象的属性和方法,并且新创建的对象可直接访问父类原型对象上的属性和方法,也可以访问父类构造函数中复制的属性和方法。验证如下:
Son.prototype = new Parent();来实现的,而instance是用来判断前面的对象是否是后面对象的实例,因此这个结果是可以理解的(实例和继承不等同),按我们的实现Son.prototype才是Parent的实例。验证如下:
构造函数继承
直接上例子:
function Parent (father) { // 声明父类
this.father = father;
this.problem = ['123', '456'];
};
Parent.prototype.getFather = function () { // 为父类添加共有方法
return this.father;
};
function Son (father, son) {
this.son = son;
Parent.call(this, father);
};
实现构造函数继承的精华Parent.call(this, father);,由于call方法可以更改函数的作用环境,因此在子类中,对Parent调用这个方法就是将子类中的变量在父类执行一遍,由于父类中是给this绑定属性的,因此子类自然也就继承了父类的共有属性。由于这种类型的继承没有涉及原型prototype,所以父类的原型方法自然不会被子类继承,而如果想被子类继承就必须放在构造函数中,这样创建出来的每个实例都会单独拥有一份而不能共用。验证如下:
组合继承
遵循惯例直接由例子入手:
function Parent (father) { // 声明父类
this.father = father;
this.problem = ['123', '456'];
};
Parent.prototype.getFather = function () { // 为父类添加共有方法
return this.father;
};
function Son (father, son) {
this.son = son;
Parent.call(this, father);
};
Son.prototype = new Parent();
Son.prototype.getSon = function () { // 为子类添加共有方法
return this.son;
};
由于组合继承就是类式继承和构造函数式继承,因此直接验证结果:
到这里此次学习记录已经结束了,如果对继承感兴趣可以再去学习一下寄生组合式继承以及多继承,其中多继承又可以扩展深拷贝和浅拷贝这一知识点。
以上都是个人的学习记录,如有理解不正确的地方,还希望各位大佬能够指出。