js面向对象编程
创建对象
创建方法
- new
let a = new Object();
a.name = "xiaoming";
console.log(a); // => {name: 'xiaoming'}
- {}
let b = {};
b.name = "dong";
console.log(b); // => { name: 'dong' }
创建模式
本质上只有上面两种方式创造对象,但是如果创建很多相同的对象用这种方法的话,会产生很多重复代码,所以需要有别的模式来创造很多对象。
let p1 = new Object();
p1.name = 'xiaohong';
p1.say = function(){
console.log("I am " + this.name);
}
p1.say();
当你需要创建很多个结构一样的对象都要这样复制一遍,那就太蛋疼了。所以去掉这些重复的代码就用到了工厂模式。
工厂模式
function createPerson(name){
let o = new Object();
o.name = name;
o.say = function(){
console.log("I am " + o.name);
}
return o;
}
let p1 = createPerson('xiaohong');
p1.say();
let p2 = createPerson('ming');
p2.say();
/*
I am xiaohong
I am ming
*/
这样改了之后创建对象就简单很多了,基本实现了代码复用。但是有个缺陷就是没办法识别不同类型的对象,只知道是Object对象。
构造函数模式
function Person(name){
this.name = name;
this.say = function(){
console.log("I am " + this.name);
}
/** 和上面函数逻辑是一样的
* this.say = new Function("console.log("I am " + this.name)")
*/
}
let p1 = new Person('hong');
p1.say();
let p2 = new Person('ming');
p2.say();
console.log(p1 instanceof Person);
console.log(p1.say == p2.say);
/*
I am hong
I am ming
true
false
*/
使用构造函数已经可以识别对象的类型了,每创建一个对象,属性和方法都会复制一份,对于属性每个对象都有一份挺正常的,但是没必要需要多份完成一样任务的方法。
原型模式
每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。 既该构造函数的所有实例共享prototype里的内容。每创建一个函数,就会同时创建它的 prototype 对象,这个对象也会自动获得 constructor 属性
function Person(){
}
Person.prototype.name = 'hua';
Person.prototype.friends = ['gang', 'li'];
Person.prototype.say = function(){
console.log("I am " + this.name + " friends:" + this.friends);
}
let p1 = new Person();
p1.name = 'ming';
p1.friends.push('qiang');
let p2 = new Person();
p2.name = 'hong';
p2.friends.push('chen');
p1.say();
p2.say();
console.log(p1.say == p2.say);
/*
I am ming friends:gang,li,qiang,chen
I am hong friends:gang,li,qiang,chen
true
*/
p1和p2都有一个 __proto__
指向了Person的 prototype 对象,原型中所有属性是被很多实例共享的,这种共享对于函数非常合适。对于那些包含基本值的属性倒也说得过去,毕竟(如前面的例子所示),通过在实例上添加一个同名属性,可以隐藏原型中的对应属性。然而,对于包含引用类型值的属性来说,问题就比较突出了。
组合使用构造函数模式和原型模式
function Person(name){
this.name = name;
this.friends = ['gang', 'li'];
}
Person.prototype.say = function(){
console.log("I am " + this.name + " friends:" + this.friends);
}
let p1 = new Person('hong');
p1.friends.push('qiang');
let p2 = new Person('ming');
p2.friends.push('chen');
p1.say();
p2.say();
/*
I am hong friends:gang,li,qiang
I am ming friends:gang,li,chen
*/
这样创建出来的每个对象都是独享属性,共享方法了。
继承
这个时候如果我想新创建一种对象Student,这个对象有Person所有的属性和方法,另外还有属于Student的新属性和方法,那么我要把Person的内容再复制一遍吗?这样明显没有做到代码复用,这个时候就要用到了prototype来实现继承了。
原型链
如果我们将Student的prototype
指向 new Student()
,那么就有了定义在Person上的name和在Person.prototype
上的say(),但是也会出现一个问题,如上面所提到的name
属性放到了prototype
里面,里面的属性会被共享。
function Person(){
this.name = 'xiaoming';
this.friends = ['xiaogang'];
}
Person.prototype.say = function(){
console.log("I am " + this.name + ", friends:" + this.friends);
}
function Student(){
this.interest = 'study';
}
Student.prototype = new Person();
Student.prototype.getInterest = function(){
console.log("My interest is: " + this.interest);
}
let s = new Student();
s.friends.push('xiaohua');
s.say();
s.getInterest();
let s2 = new Student();
s2.say();
s2.getInterest();
/*
I am xiaoming, friends:xiaogang,xiaohua
My interest is: study
I am xiaoming, friends:xiaogang,xiaohua
My interest is: study
*/
组合继承
function Person(name){
this.name = name;
this.friends = ['xiaogang'];
}
Person.prototype.say = function(){
console.log("I am " + this.name + ", friends:" + this.friends);
}
function Student(name){
// 在子类型构造函数里面调用父类型构造函数
Person.call(this, name);
this.interest = 'study';
}
Student.prototype = new Person();
Student.prototype.getInterest = function(){
console.log("My interest is: " + this.interest);
}
let s = new Student('hong');
s.friends.push('xiaohua');
s.say();
s.getInterest();
let s2 = new Student('hua');
s2.say();
s2.getInterest();
/*
I am hong, friends:xiaogang,xiaohua
My interest is: study
I am hua, friends:xiaogang
My interest is: study
*/
函数是在特定环境执行的代码,在子类型构造函数里面调用父类型构造函数,上面的代码则相当与复制了一份Person
里的属性到Student
中,这样就避免了 Student
的实例共享一份属性。组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。这样就有重复的两份属性,一份在Student
实例本身,一份在Student.prototype
里面。
寄生组合式继承
因为子类型Student
会在构造函数里面调用父类型Person
的构造函数,这样子类型Student
就有了一份父类型的属性name
和friends
,所以Student.prototype
需要的仅仅是Person.prototype
的内容而已
/**
* 寄生组合式继承
*/
function object(o){
function F(){}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
function Person(name){
this.name = name;
this.friends = ['xiaogang'];
}
Person.prototype.sayName = function(){
alert(this.name);
};
function Student(name, age){
Person.call(this, name);
this.age = age;
}
inheritPrototype(Student, Person);
Student.prototype.sayAge = function(){
alert(this.age);
};
let instance = new Student('xiaohua', 29);
console.log(instance);
instance
结构如下
原型链如下:
class继承
/**
* class 继承
*/
class Person{
constructor(name){
this.name = name;
}
say(){
console.log('my name is:' + this.name);
}
}
class Student extends Person{
constructor(name, age) {
super(name);
this.age = age;
this.interest = 'play';
}
getInterest(){
console.log('my interest is:' + this.interest);
}
}
let s = new Student('xiaohua', 18);
s.say();
s.getInterest();
/*
my name is:xiaohua
my interest is:play
*/
es6新引入关键字class
,利用class
来代替 Person.prototype.say
这样的代码,同时用extends
来实现继承。