参考视频:2019全新javaScript进阶面向对象ES6
参考文档:ES6入门教程
文章目录
面向对象特征
- (抽象)
- 封装
- 继承
- 多态
ES6类
类class
- 创建类:
class Person{
//body
}
- 创建对象/实例:
var x = new Person();
必须使用new
。
ES6中的类没有变量提升:先定义类,才能实例化。不可颠倒。
- 添加函数/方法:
//in class
myFunction(str){ //4种JS输出
console.log(str);
alert(str);
document.write(str);
//document.getElementById("myid").innerHTML = str;
return 0;
}
不加function
!
不加,
!
调用自己:this对象
类对象的共有属性和方法通过this
调用。
- 注意this的指向:
<button>按钮</button>
<script>
class Person{
constructor(name) {
this.btn = document.querySelector("button");
this.btn.onclick = this.f;
//加小括号:加载页面后就调用
//不加小括号:点击按钮触发事件才会调用
}
f(){
console.log(this); //使用this
}
}
var x = new Person();
</script>
- 代码结果
此处this
指向当前对象中的按钮,而不是当前对象。
- 构造器中
this
指向当前实例对象。 - 方法中
this
指向当前调用者对象。
构造器constructor
在使用new
实例化时,调用构造器,进行赋值等初始化操作。
不加构造器时,系统会默认添加一个空构造器。
构造器唯一(和Java不同)。
- 创建:
class Person{
constructor(name, age) {
this.name = name;
this.age = age;
}
}
- 通过构造器实例化:
var x = new Person("myname", 20);
- 可使用:
console.log(x.age);
console.log(x);
- 代码结果:
继承extends
class Father{
}
class Son extends Father{
}
子类对象可以使用父类已有的属性和方法。
写和父类同名的方法:重写。
写和父类不同名方法:扩展。
调用父类:super对象
- 调用父类构造器:
class Father{
constructor(name, age) {
this.name = name;
this.age = age;
}
}
class Son extends Father{
constructor(name, age) {
super(name, age); //调用父类(构造器)
//super必须放在this前面
this.name = name;
this.age = age;
}
}
- 调用父类普通函数:
class Father{
say(){
return "in Father";
}
}
class Son extends Father{
say(){ //重写
console.log(super.say()); //调用父类方法
}
}
Javascript对象
ES6之前Javascript没有类,但是有对象的概念,而且所有事物都是对象。
定义对象
- 定义对象(使用变量):
/*对象*/
var Person={
/*属性*/
name:"abc",
age:30,
/*方法*/
echo:function(str){
alert(str);
}
}
- 定义对象(使用函数作为构造器):
/*contructor*/
function Person(nameText, ageText) {
this.name = nameText;
this.age = ageText;
this.f = function(){
console.log("in function f");
}
}
var p = new Person("abc", 30);
document.write(p.name);
document.write(p.age);
p.f();
实例成员和静态成员
function Person(nameText) {
this.name = nameText; //实例成员
}
Person.age = 30; //静态成员
console.log(new Person("myname").name);
console.log(Person.age);
prototype原型对象与__proto__原型属性
每一个构造函数都有一个prototype
属性(对象),即原型对象。
不变的方法可以直接定义在prototype
上,让对象实例共享方法。节约内存空间。
prototype
原型对象(属于构造函数):
function Person(nameText) {
this.name = nameText;
}
Person.prototype.f = function(str){ //原型
console.log(str);
}
var t = new Person("myname");
t.f(t.name); //调用测试
- 代码结果
__proto__
原型属性(属于对象实例):
function Person(nameText) {
this.name = nameText;
}
Person.prototype.f = function(str){ //原型
console.log(str);
}
var t = new Person("myname");
console.log(t); //打印t对象
console.log(t.__proto__ === Person.prototype); //t中__proto__与构造函数的prototype是否严格相等
- 代码结果(对象属性):
- 代码结果(严格相等):
足以证明实例对象的原型属性和构造函数的原型对象是相同的。
constructor构造函数
__proto__
和prototype
都含有一个constructor
属性,指回构造函数本身。
- 构造函数属性:
function Person(nameText) {
this.name = nameText;
}
Person.prototype.f = function(str){ //原型
console.log(str);
}
var t = new Person("myname");
console.log(t.__proto__);
console.log(Person.prototype);
- 代码结果:
- 构造函数属性测试:
function Person(nameText) {
this.name = nameText;
}
Person.prototype.f = function(str){ //原型
console.log(str);
}
var t = new Person("myname");
console.log(t.__proto__.constructor);
console.log(Person.prototype.constructor);
- 代码结果
- 使用constructor指回构造函数1:
function Person(nameText) {
this.name = nameText;
}
Person.prototype = { //原型,一次性添加多个函数
f1: function (str) {
console.log(str);
},
f2: function (str) {
alert(str);
}
//相对于单次添加函数Person.prototype.f(){},会覆盖掉constructor!
}
console.log(Person.prototype.constructor); //批量添加原型后,构造函数
Person.prototype.constructor = Person; //手动指回
console.log(Person.prototype.constructor); //批量添加原型并手动指回后,构造函数
- 代码结果
- 使用constructor指回构造函数2:
function Person(nameText) {
this.name = nameText;
}
Person.prototype = { //原型,一次性添加多个函数
constructor: Person, //手动指回
f1: function (str) {
console.log(str);
},
f2: function (str) {
alert(str);
}
//相对于单次添加函数Person.prototype.f(){},会覆盖掉constructor!
}
console.log(Person.prototype.constructor);
- 代码结果:
原型链
- (constructor、prototype、实例对象)三者关系
- 原型链
-
- 函数原型对象
prototype
也有__proto__
属性,和Object对象的prototype
严格相等。
function Person(nameText) {
this.name = nameText;
}
console.log(Person.prototype.__proto__ === Object.prototype);
-
代码结果:true
-
Object对象的
prototype
中的__proto__
属性指向null。
console.log(Object.prototype.__proto__);
- 代码结果:null
基于原型链的成员查找机制
this指向问题
- 一般情况下,this指向调用者。
- 构造函数中,this指向实例对象
function Person(nameText) {
this.name = nameText; //this指向对象实例
}
- 函数原型中,this指向实例对象
function Person(nameText) {
this.name = nameText;
}
Person.prototype.f = function(){
console.log(this); //this指向对象实例
}
var t = new Person("myname");
t.f(); //调用
- 代码结果:
使用原型对象扩展内置对象
- 打印Array原型对象:
console.log(Array.prototype);
- 代码结果:
- 为Array类添加新函数(求和):
Array.prototype.sum = function () {
var res = 0;
var len = this.length;
for(let i = 0; i < len; i++){
res += this[i];
}
return res;
}
t = [1, 2, 3];
console.log(t.sum());
继承
ES6之前通过构造函数和原型函数实现组合继承。
call方法
调用指定函数。
修改函数运行时this
指向。
- 调用函数:
function f() {
console.log("my function");
}
f.call(); //显式调用函数
- 改变指向:
- this指向默认window
function f() {
console.log(this); //调用者:window
}
f.call();
- 代码结果
- 使this指向新对象:
function f() {
console.log(this); //调用者:window
}
var newobject = {age: 20};
f.call(newobject); //更改调用者
- 代码结果
- 使this指向新对象(增加新参数):
function f(x, y) {
console.log(this); //调用者:window
}
var newobject = {age: 20};
f.call(newobject, 1, 2); //第一个参数更改调用者,后续为普通参数
继承父类属性:构造函数
通过call()
将父类型的this指向子类型的this。
function Father(name) { //父类
this.name = name;
}
function Son(name) { //子类
Father.call(this, name); //父类的this变成了子类的this,再添加参数
this.age = age; //新加属性
}
var t = new Son("myname");
console.log(t.name); //验证
继承父类属性:原型对象
- 指向父类原型:
function Father(name) { //父类
this.name = name;
}
Father.prototype.getName = function(){ //父类原型
return this.name;
}
function Son(name) { //子类
Father.call(this, name); //父类的this变成了子类的this
}
Son.prototype = Father.prototype; //子类原型指向父类原型
var t = new Son("myname");
console.log(t.getName()); //验证
会出现问题:
- 子类原型新添加方法时,会给父类也添加此方法。
- 子类的构造器变成了父类的构造器。
- 指向父类实例:
function Father(name) { //父类
this.name = name;
}
Father.prototype.getName = function(){ //父类原型
return this.name;
}
function Son(name) { //子类
Father.call(this, name); //父类的this变成了子类的this
}
//Son.prototype = Father.prototype; //子类原型指向父类原型
Son.prototype = new Father(); //先实例化,再让子类原型指向父类实例对象
Son.prototype.constructor = Son; //指向父类实例后会失去构造器,重新指回
var t = new Son("myname");
console.log(t); //验证
- 代码结果:在子类实例的
__proto__
中的__proto__
中找到继承的父类方法。
ES6类的本质
- 测试类的本质:
- 测试代码
class MyClass{}
console.log(typeof MyClass); //测试类型
console.log(MyClass.prototype); //测试原型函数
console.log(MyClass.prototype.constructor); //测试构造器
var t = new MyClass();
console.dir(t); //测试实例
-
代码结果:
-
结论:
类的本质是函数,可理解为构造函数的另一种形式。
类有原型对象。
类有构造器。
实例有__proto__
属性。
- 使用原型为类添加函数:
- 测试代码:
class MyClass{}
console.log(MyClass.prototype);
MyClass.prototype.printNew = function(){
console.log("new!");
}
含有变量/函数提升。
- 代码结果:
- ES6的类是语法糖:
两种方法实现同种功能,更便捷的写法,便是语法糖。
ES5新增方法
数组与字符串方法
参考文档:ECMAScript 5 - JavaScript 5
新的对象属性和方法
Object.defineProperty
vue
中会用到。
//定义对象
var obj = {
id: 1,
name: "myname"
};
//旧的方式
obj.age = 20; //新增属性
obj.id = 2; //修改属性
//新的方式
Object.defineProperty(obj, 'sum', { //新增属性
value: 100
});
Object.defineProperty(obj, 'name',{ //修改属性
value: "mynametwo", //值,默认undefined
writable: true, //是否可以被重写/修改,默认false
enumerable: true, //是否可以被枚举,默认false
configurable:true //是否可以被删除/修改特性true or false,默认false
})
//get和set
Object.defineProperty(obj, "name", {
get : function() { return name },
set : function(value) { name = value}
});
writable
(可修改):默认false
var obj = {
s0: 100
};
Object.defineProperty(obj, 's1', {
value: 100
});
Object.defineProperty(obj, 's2',{
value: 100,
writable: true
});
//测试
obj.s0 += 1; //成功,不是通过Object.defineProperty定义的
obj.s1 += 1; //失败,默认false不允许修改
obj.s2 += 1; //成功,手动true允许修改
console.log(obj);
enumerable
(可枚举/可遍历):默认false
var obj = {
s0: 100
};
Object.defineProperty(obj, 's1', {
value: 100
});
Object.defineProperty(obj, 's2',{
value: 100,
enumerable: true
});
//测试
console.log(obj);
console.log(Object.keys(obj));
//s0成功,不是通过Object.defineProperty定义的
//s1失败,默认false不允许枚举
//s2成功,手动true允许枚举
configurable
(可删除/修改特性true or false):默认false
var obj = {
s0: 100
};
Object.defineProperty(obj, 's1', {
value: 100
});
Object.defineProperty(obj, 's2',{
value: 100,
configurable: true
});
//测试
delete obj.s0; //成功,不是通过Object.defineProperty定义的
delete obj.s1; //失败,默认false不允许删除
delete obj.s2; //成功,手动true允许删除
console.log(obj);
var obj = {
s0: 100
};
Object.defineProperty(obj, 's1', {
value: 100
});
Object.defineProperty(obj, 's2',{
value: 100,
configurable: true
});
//测试
Object.defineProperty(obj, 's0', { //成功,不是通过Object.defineProperty定义的
writable: true,
enumeralbe: true
});
Object.defineProperty(obj, 's1', { //失败(异常),默认false不允许更改特性
enumeralbe: true
});
Object.defineProperty(obj, 's2', { //成功,手动true允许更改特性
writable: true,
enumeralbe: true
});
console.log(obj);