Js 面向对象总结
JavaScript 中没有父类, 子类的概念, 也没有class 和 instance 的概念, 全靠
prototype chain
来实现继承.
查找一个对象的属性时, JavaScript 会向上遍历 prototype chain
, 直到找到对应的属性为止.
类方法和实例方法
这里要说的类方法其实就是静态方法。实例方法就是必须创建实例才能调用的方法
两者区别如下
- 实例方法要用
function这个对象中的prototype属性
来定义 - 静态方法通过A.直接定义
- 静态方法可以直接通过A.来调用
- 实例方法必须要new一个对象来调用。
function A(){}
A.sayMeS=function(){
console.log("Hello World S!");
} // 定义静态方法
A.sayMeS();//输出Hello World S!
var a = new A(); // 定义引用变量
a.sayMeS(); //a.sayMeS is not a function 引用变量尝试调用静态方法失败
//实例方法:
function A(){}
A.prototype.sayMeE=function(){
console.log("Hello World E!");
}
var a=new A();
a.sayMeE();//输出Hello World E! //引用变量调用实例方法
A.sayMeE(); //A.satMeE is not a function
继承
prototype继承
function Parent1() {
this.name = 'parent1';
this.play = [1, 2, 3]
}
function Child1() {
this.type = 'child2';
}
Child1.prototype = new Parent1();
console.log(new Child1());
//实现了继承
console.log(new Child2()) // 没有打印name,play
var s1 = new Child2();
var s2 = new Child2();
s1.play.push(4);
console.log(s1.play, s2.play); // 但是可以访问到name, play
缺点:
- 父类的引用属性不被共享
- 子类不能拿到父类属性值
call继承
function Parent1(){
this.name = 'parent1';
}
Parent1.prototype.getName = function () {
return this.name;
}
function Child1(){
Parent1.call(this);
this.type = 'child1'
}
let child = new Child1();
console.log(child); // 没问题
console.log(child.getName()); // 会报错
优点:子类能够拿到父类的属性值
缺点:只能继承父类的实例属性和方法,不能继承原型属性或者方法。
组合继承
function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}
Parent3.prototype.getName = function () {
return this.name;
}
function Child3() {
// 第二次调用 Parent3()
Parent3.call(this);
this.type = 'child3';
}
// 第一次调用 Parent3()
Child3.prototype = new Parent3();
// 手动挂上构造器,指向自己的构造函数
Child3.prototype.constructor = Child3;
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play); // 不互相影响
console.log(s3.getName()); // 正常输出'parent3'
console.log(s4.getName()); // 正常输出'parent3'
缺点:父类构造函数执行了两次,可能造成性能问题
Object.create继承
let parent4 = {
name: "parent4",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
let person4 = Object.create(parent4);
person4.name = "tom";
person4.friends.push("jerry");
let person5 = Object.create(parent4);
person5.friends.push("lucy");
console.log(person4.name); // tom
console.log(person4.name === person4.getName()); // true
console.log(person5.name); // parent4
console.log(person4.friends); //[ 'p1', 'p2', 'p3', 'jerry', 'lucy' ]
console.log(person5.friends); //[ 'p1', 'p2', 'p3', 'jerry', 'lucy' ]
缺点:多个实例的引用类型属性指向相同的内存
寄生组合式继承
function clone (parent, child) {
// 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
}
function Parent6() {
this.name = 'parent6';
this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () {
return this.name;
}
function Child6() {
Parent6.call(this);
this.friends = 'child5';
}
clone(Parent6, Child6);
Child6.prototype.getFriends = function () {
return this.friends;
}
let person6 = new Child6();
console.log(person6);
console.log(person6.getName());
console.log(person6.getFriends());
优点:减少了构造次数,减少了性能的开销
ES6 Extends继承
本质上采用的是 寄生组合式继承, 效果与之相同
class Person {
constructor(name) {
this.name = name
}
// 原型方法
// 即 Person.prototype.getName = function() { }
// 下面可以简写为 getName() {...}
getName = function () {
console.log('Person:', this.name)
}
}
class Gamer extends Person {
constructor(name, age) {
// 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
super(name)
this.age = age
}
}
const asuna = new Gamer('Asuna', 20)
asuna.getName() // 成功访问到父类的方法