1.1 概念
es6中新增加了类的概念,可以使用class关键字声明一个类
1.2 创建类
- 实例 类似于Java
class Star {
constructor(name) {
this.uname = name;
}
song() {
console.log(this.uname);
}
}
var zt = new Star('zt');
console.log(zt.uname);
-
constructor()构造器(默认方法)
- 用于传递参数,返回实例对象,通过new命令生成对象实例时,自动调用该方法。如果没有显示定义,类内部会自动创建一个constructor()
- constructor()构造器不需要声明function
- 由于constructor也是一个函数,因此具有arguments内置属性
-
constructor属性
-
对象间拥有一个共享的属性constructor,该属性存储当前对象的构造函数
-
实例
class Star { constructor(name) { this.uname = name; } } var zt = new Star('zt'); console.log(zt.constructor);// 返回class Star { // constructor(name) { // this.uname = name; // } // } console.log(zt.constructor == Star); // 返回true
-
-
注意
- 类方法之间不需要加逗号分隔
- 类方法不需要用function进行声明
1.3 创建对象
es6之前,js并没有引入类的概念,而目前浏览器的js是es5版本的,大多数高版本的浏览器也支持es6,不过只实现了es6部分特性和功能,并且在es6之前,对象不是基于类创建的,而是用构建函数来定义对象。
-
通过字面量方式创建
var obj = { this.uname = 'zt', this.song = function() { } }
-
通过内置对象Object创建
- 争对特殊值的对象包装,没有给其传值,作为一个空对象返回,相对于来说,其没有原型
- 案例
var obj = new Object(); obj.uname = 'zt'; obj.song = function() {};
-
通过with程序块新建属性以及方法
var obj = new Object(); with(obj) { uname = 'zt'; song = function() {}; console.log('zt'); }
-
通过Object.create()创建对象
-
使用现有的对象创建新对象,而新对象的_proto_指向现有对象,可以用于实现继承操作
-
使用
Object.create(obj,properties)
- 参数说明
- obj:对象模型,也即想要继承的对象
- properties:新建对象要创建的新属性,可选参数
-
案例
var obj = { uname: 'zt', age: 18 } var objHer = Object.create(obj); console.log(objHer.uname);// 直接使用,继承父对象obj,通过原型链的方式进行访问
-
-
通过class创建
class Star { constructor(uname) { this.uname = uname; } song() {} }; var zt = new Star('zt');
-
通过构造函数创建对象
-
构造函数是一个特殊的函数,主要用来初始化对象,即为对象变量赋初始值,总是与new一起使用。
-
new在执行时会做的事件
- 在内存中创建一个新的空对象
- 让this指向这个新的对象
- 执行构造函数里面的代码,给这个新对象添加属性和方法
- 返回这个新对象,所以构造函数里面不需要return
-
语法:
function Star(uname, age) { this.uname = uname; this.age = age; this.song = function () { } } var zt = new Star('zt',18);
-
成员:构造函数中的属性和方法称为成员,成员可以添加
- 实例成员
- 构造函数内部通过this添加的成员
- 只能通过实例化对象来访问
- 静态成员
- 在构造函数本身上添加的成员
- 只能通过构造函数来访问
- 实例成员
-
构造函数使用上存在的问题
-
存在浪费内存的问题
-
实际情况:同一个构造函数创建两个实例,如果构造函数中有方法(复杂数据类型),则创建一个对象都会再开辟一个空间存储方法,因此两个实例对象的方法是存储在不同的空间中的,当实例对象数量上增加时,内存消耗也会同步增加。
-
为了所有对象使用同一个函数,且解决空间,解决方式是:使用构造函数原型:prototype
-
构造函数通过原型分配的函数是所有对象所共享的
-
js规定,每个构造函数都有一个prototype属性。而该属性指向的是对象,该对象的所有属性和方法,都会被构造函数所拥有。
-
对象如何使用prototype属性中的方法?
- 通过对象原型_proto_
- **对象都有一个属性_proto_**指向构造函数的prototype原型对象
- 因此方法的查找规则:先查看当前对象上是否有方法,如果没有则通过属性_proto_来对prototype方法中进行查找
-
注意:
- 把不变的方法,直接定义在prototype对象上
- 把属性直接定义在构造函数内部
- _proto_对象原型的意义在于为对象的查找机制提供一个方向,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象prototype
-
原型:一个对象,也称为prototype为原型对象
-
原型的作用:共享方法
-
constructor属性
-
对象原型_proto_和构造函数原型对象prototype对象里面都有一个属性constructor属性
-
constructor主要用于记录该对象引用哪个构造函数,可以让原型对象重新指向原来的构造函数
-
执行过程:
-
当给构造函数原型对象prototype赋值为一个对象时,
class Star() { } Star.prototype = { sing:function(){} } var zt = new Star(); console.log(zt._proto_.constructor); console.log(Star.prototype.constructor); // 此时以上的constructor不再指向构造函数本身
-
解决:手动利用constructor指回原来的构造函数
class Star() { } Star.prototype = { constructor:Star, sing:function(){} } var zt = new Star(); console.log(zt._proto_.constructor); console.log(Star.prototype.constructor); // 此时以上的constructor重新指向构造函数本身
-
-
-
-
-
构造函数、实例、原型对象三者之间的关系
-
构造函数通过属性prototype指向原型对象,原型对象prototype通过其属性constructor指向构造函数
-
构造函数通过new方法创建了对象实例,对象实例通过属性_proto_指向原型对象
-
关系图:
-
原型链
-
由于每个对象都有_proto_属性,因此当前原型对象的_proto_会指向Object的prototype对象,而Object的原型对象指向null,而这个_proto_属性的不断指向prototype对象的过程称为原型链
-
js成员查找机制
- 当访问一个对象的成员时,首先查找这个对象本身有没有该属性
- 如果没有就查找它的原型(_proto_指向的prototype原型对象)
- 如果还没有就查找原型对象的原型(Object的原型对象)
- 依次类推,一直找到Object为止(null)
-
例子:Object.prototype里面有toString方法,但是除非手动添加该方法,其他对象中没有该方法,但是其他对象仍然可以使用该方法:原型链存在的原因
-
-
-
1.4 类的继承
-
es6之前组合继承的方式:构造函数+原型对象模拟实现继承
-
call(thisArg,arg1,arg2,…)
-
参数说明
- thisArgs:当前调用函数this的指向对象
- args…:传递的其他参数
-
作用
- 调用函数
- 修改函数运行时的this指向
-
调用函数
function print_s() { console.log('s'); console.log(this);// 此时指向window对象 } print_s.call();
-
修改函数运行时的this指向
function print_s(x,y) { console.log('s'); console.log(this);// 此时指向example_o console.log(x + y); } var example_o = { uname: 'zt' } print_s.call(example_o,1,2);
-
-
通过构造函数实现继承父构造函数属性
function Father(uname,age) { // this 指向父构造函数的对象实例 this.uname = uname; // this(Son).uname = uname this.age = age; } function Son(uname,age,score) { // this 指向Son构造函数的对象实例 // 通过call 调用当前函数时,修改父类的指向,从而使得父类属性改为了子类属性 Father.call(this,uname,age); this.score = score; } var son = new Son('zt',18,100); console.log(son);
-
通过原型对象实现继承父构造函数方法
-
如果只是通过Son.prototype = Father.prototype方式进行赋值,虽然这样使得Son.prototype指向了父构造函数,但是由于对同一个对象的修改会导致同时被修改,因此当通过追加son.prototype中存储的方法的时候,父构造函数prototype也会相应的被修改
function Father(uname,age) { this.uname = uname; this.age = age; } function Son(uname,age,score) { Father.call(this,uname,age); this.score = score; } Father.prototype.test = function () {}; Son.prototype = Father.prototype; // 不可取
-
为了解决上述问题,通过原型链的方式来解决
function Father(uname,age) { this.uname = uname; this.age = age; } function Son(uname,age,score) { Father.call(this,uname,age); this.score = score; } Father.prototype.test = function () {}; Son.prototype = new Father();
由于实例对象与Father原型对象是互不相关的两个对象,因此当让son.prototype指向Father实例的时候,由于new Father()实例的_proto_指向的是Father中的原型对象,因此根据原型链的思路,如果在当前prototype对象中找不到改方法,会在Father原型对象中进行查找,另一方面,当Son.prototype追加方法的时候,不会影响父构造函数原型对象中的内容。但是还是存在一个问题,son.prototype.constructor指向的是Father构造函数,为了解决,手动添加:
function Father(uname,age) { this.uname = uname; this.age = age; } function Son(uname,age,score) { Father.call(this,uname,age); this.score = score; } Father.prototype.test = function () {}; Son.prototype = new Father(); son.prototype.constructor = Son;
-
-
-
es6_关键字extends
-
语法
class Father { sum() { console.log(1); } } class Son extends Father { } var son = new Son(); son.sum();
-
子类继承了父类的方法,即可以使用父类的方法
-
当父类方法中使用了父类自己的属性的时候,需要在子类中进行调用父类的构造器将属性传递过去,采用super关键字
- super关键字用于访问和调用对象父类上的函数。
- 可以调用父类的构造函数
- 可以调用父类的普通函数
class Father { constructor(x,y) { this.x = x; this.y = y; } sum() { console.log(this.x + this.y); } } class Son extends Father { constructor(x,y) { super(x,y);// 调用父类的构造函数 } sum() { super().sum();// 调用父类的普通函数 } } var son = new Son(1,2); son.sum();
-
继承中的属性/方法查找原则:就近原则,先看子类中有没有这个方法,没有再看父类中有没有这个方法
-
注意点
- 子类在构造函数中使用super,必须放在this前面,也即必须先调用父类的构造方法,在使用子类的构造方法
- 在es6中类没有变量提升,所以必须先定义类,才能通过类实例化对象
- 类里面的共有属性和方法一定要加this使用
-
this指向调用者
-
1.5 类的本质
- 类的本质也是function
- es6类实际上也是一个语法糖
- 类同样含有prototype属性对象以及所创建的对象含有_proto_属性对象,也即含有构造函数的所有用法,只是es6进一步的封装了
- 类的所有方法都定义在类的prototype属性上