JS面向对象编程

目录

1.创建对象

ECMAScript中没有类的概念,可以把 ECMAScript的对象想象成一张散列表,其中的内容就是一组名/值对
属性的类型分两种:数据属性和访问器属性

(1)工厂模式:每次返回都会返回包含属性和方法的对象

function createPerson(name, age, job) {
    let o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        console.log(this.name);
    };
    return o;
}
let person1 = createPerson("Nicholas", 29, "Software Engineer");
let person2 = createPerson("Greg", 27, "Doctor");

(2)构造函数模式

person1 和 person2 分别保存着 Person 的不同实例。这两个对象都有一个constructor 属性指向 Person 

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        console.log(this.name);
    };
}
let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Greg

constructor属性instanceof 操作符都可以用来确定对象类型 

console.log(person1.constructor == Person); // true
console.log(person1 instanceof Person); // true

优点:相比于工厂模式,自定义构造函数可以确保实例被标识为特定类型
缺点: 其定义的方法会在每个实例上都会重新创建一遍

(3)原型模式 

每个函数都会创建一个 prototype 属性,这个属性指向一个原型对象。该对象可以让所有对象实例共享一些属性和方法。 
原型对象有一个名为 constructor 的属性,指回与之关联的构造函数。
每次调用构造函数创建一个新实例,该实例内部的[[Prototype]]指针就会指向构造函数的原型对象。
也就是说:实例与构造函数原型之间有直接的联系,但与构造函数之间没有。

function Person() {} //使用函数表达式也可以let Person = function() {};
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
    console.log(this.name);
};
let person1 = new Person();
person1.sayName(); // "Nicholas"
let person2 = new Person();
person2.sayName(); // "Nicholas"
console.log(person1.sayName == person2.sayName); // true

isPrototypeOf()方法可以确定对象实例和构造函数之间的关系

console.log(Person.prototype.isPrototypeOf(person1)); // true

Object.getPrototypeOf() 方法,返回对象实例的内部特性[[Prototype]]的值,也就是原型对象。

console.log(Object.getPrototypeOf(person1) == Person.prototype); // true

通过对象访问属性时,会先搜索对象实例本身,如果该实例上有指定属性,则返回对应的值。如果没有,就会沿着指针进入原型对象,到原型对象上继续搜索。 

hasOwnProperty() 方法用于确定某个属性是在实例上还是在原型对象上,在实例上返回true,在原型对象上返回false。

//还是上面那个构造函数
let person1 = new Person();
let person2 = new Person();

person1.name = "Greg";
console.log(person1.hasOwnProperty("name")); // true

console.log(person2.name); // "Nicholas",来自原型
console.log(person2.hasOwnProperty("name")); // false

delete person1.name; //delete可以删除实例上的属性
console.log(person1.hasOwnProperty("name")); // false

原型和 in 操作符 

in 操作符会在可以通过对象访问指定属性时返回 true ,无论该属性是在实例上还是在原型上。

// 确定某个属性是否存在于原型上
function hasPrototypeProperty(object, name){
    return !object.hasOwnProperty(name) && (name in object);
}

for-in循环中使用in操作符,可通过对象访问且可被枚举的属性都会返回,包括实例属性和原型属性。 
获得对象上所有可枚举的实例属性,可以使用 Object.keys()
想列出所有实例属性,无论是否可以枚举,可使用Object.getOwnPropertyNames()

更简单的写法

function Person() {
}
Person.prototype = {
    //用字面量的形式创建创建一个原型对象,此时constructor属性指向Object对象
    //所以这里要指回去
    constructor: Person, 
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName() {
        console.log(this.name);
    }
};

缺点:原型上的所有属性是在实例间共享的,但一般来说,不同的实例应该有属于自己的属性副本

(4)组合使用构造函数模式和原型模式

构造函数用于定义实例属性
原型对象用于存放共享的方法和属性

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}
Person.prototype = {
    constructor: Person,
    sayName: function() {
        console.log(this.name);
    }
}
let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court"

2.继承

①原型链 

由于接口继承只继承方法签名,而由于JS中的函数没有签名,所以JS无法实现接口继承
可以依靠原型链来实现继承
基本思想: 利用原型让一个引用类型继承另一个引用类型的属性和方法
所有引用类型都继承自 Object ,这也是通过原型链实现的

function SuperType() {
    this.property = true;
}
SuperType.prototype.getSuperValue = function() {
    return this.property;
};
function SubType() {
    this.subproperty = false;
}
// 继承 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
    return this.subproperty;
};
let instance = new SubType();
console.log(instance.getSuperValue()); // true

 确定原型与继承关系

// instanceof 操作符
console.log(instance instanceof Object); // true
console.log(instance instanceof SuperType); // true
console.log(instance instanceof SubType); // true
// isPrototypeOf() 方法
console.log(Object.prototype.isPrototypeOf(instance)); // true
console.log(SuperType.prototype.isPrototypeOf(instance)); // true
console.log(SubType.prototype.isPrototypeOf(instance)); // true

以对象字面量方式创建原型方法会破坏之前的原型链

// 继承 SuperType
SubType.prototype = new SuperType();
// 通过对象字面量添加新方法,这会导致上一行无效
SubType.prototype = {
    getSubValue() {
        return this.subproperty;
    },
    someOtherMethod() {
        return false;
    }
};
let instance = new SubType();
console.log(instance.getSuperValue()); // 出错!

问题

①对象实例共享所有继承的属性和方法
②在创建子类型的实例时,不能向超类型的构造函数中传递参数 

②借用构造函数

基本思路:在子类构造函数中调用父类构造函数。
相比于使用原型链的优点:可以在子类构造函数中向父类构造函数传参。 

function SuperType(name){
    this.name = name;
}
function SubType() {
    SuperType.call(this, "Nicholas"); // 继承 SuperType 并传参
    this.age = 29; // 实例属性
}
let instance = new SubType();
console.log(instance.name); // "Nicholas";
console.log(instance.age); // 29

问题:必须在构造函数中定义方法,因此函数不能重用。

③组合继承

综合了原型链和盗用构造函数

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
    console.log(this.name);
};
function SubType(name, age){
    // 继承属性
    SuperType.call(this, name);     // 第二次调用SuperType构造函数
    this.age = age;
}
// 继承方法
SubType.prototype = new SuperType(); // 第一次调用SuperType构造函数
SubType.prototype.sayAge = function() {
    console.log(this.age);
};

let instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
instance1.sayName(); // "Nicholas";
instance1.sayAge(); // 29
let instance2 = new SubType("Greg", 27);
console.log(instance2.colors); // "red,blue,green"
instance2.sayName(); // "Greg";
instance2.sayAge(); // 27

 问题:会调用两次超类型的构造函数,见上

④寄生式组合继承

function inheritPrototype(subType, superType) {
    let prototype = object(superType.prototype); // 创建对象
    prototype.constructor = subType; // 增强对象
    subType.prototype = prototype; // 赋值对象
}
function SuperType(name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
    console.log(this.name);
};
function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
    console.log(this.age);
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

漂流の少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值