JavaScript面向对象编程

基本都是廖学峰老师JavaScript教程的内容。先前看也似是懂了,本身稍微理下做复习。javascript

一、原型链

JavaScript 的原型链和 Java 的 Class 区别就在, 它没有“Class”的概念, 全部对象都是实例,所谓继承关系不过是把一个对象的原型指向另外一个对象而已。java

Student是一个现有的对象,不一样于C++,Python中类对象的概念,Student更符合实例对象。Studentname属性和run()方法。xm只有name属性,Studentxm是同等地位的实例对象。xm.__proto__ = Student;xm的原型指向了对象Student,看上去 xm仿佛是从Student继承下来的。xm能够调用Studentrun()方法。数组

var Student = {
    name: 'Robert',
    run: function() {
        console.log(this.name + ' is running...');
    }
};

var xm = {
    name: 'xiaoming'
}

xm.__proto__ = Student; 

console.log(xm.name);
xm.run();
console.log(Student.name);
Student.run();

// run output:
// xiaoming
// xiaoming is running...
// Robert
// Robert is running...

上述代码仅用于演示目的。在编写 JavaScript 代码时,不要直接用 obj.__proto__去改变一个对象的原型,而且,低版本的IE也没法使用__proto__


Object.create()方法能够传入一个原型对象,并建立一个基于该原型的新对象, 可是新对象什么属性都没有。能够编写一个函数来建立新对象。函数

xm对象的__proto__属性是{ name: 'Robert', run: [Function: run] },而没有prototype属性。this

var Student = {
    name: 'Robert',
    run: function() {
        console.log(this.name + ' is running...');
    }
};
function createStudent(name) {
    // 传入一个原型对象,建立一个新的原型对象
    var s = Object.create(Student);
    s.name = name;
    return s;
}
var xm = createStudent('xiaoming');
xm.run()    // xiaoming is running...
// xm 对象的__proto__ 和 prototype
console.log(xm.__proto__)   //{ name: 'Robert', run: [Function: run] }
console.log(xm.prototype)   // undefined

// 原型链
console.log(xm.__proto__ === Student);
console.log(Student.__proto__ === Object.__proto__.__proto__);
console.log(Object.__proto__.__proto__.__proto__ === null);

对象xm原型链为:
xm --> Student.__proto__ --> Object.__proto__ --> null

prototype

二、构造函数

除了直接用{ ... }建立一个对象外,JavaScript还能够用一种构造函数的方法来建立对象。它的用法是,先定义一个构造函数,再用关键字new来调用这个函数,并返回一个对象。构造函数和普通函数并没有差异。若是不写new,它就是一个普通函数,它返回undefined。可是,若是写了new,它就变成了一个构造函数,它绑定的this指向新建立的对象,并默认返回this,也就是说,不须要在最后写return thiscode

function Student(name) {
    this.name = name;
    this.hello = function () {
        console.log('Hello, ' + this.name);
    };
}

var jack = new Student('jack');

console.log(jack.name);   //jack
jack.hello();     //Hello, jack

console.log(jack.__proto__ === Student.prototype)
console.log(Student.prototype.__proto__ === Object.prototype)
console.log(Object.prototype.__proto__ === null)

对象jack的原型链是:
jack --> Student.prototype --> Object.prototype --> null
用new Student()建立的对象还从原型上得到了一个constructor属性,它指向函数Student自己:对象

console.log(jack.constructor === Student.prototype.constructor) // true
console.log(Student.prototype.constructor === Student) // true
console.log(Object.getPrototypeOf(jack) === Student.prototype) // true
console.log(jack instanceof Student) // true


三、__proto__和prototype
JavaScript 对每一个建立的对象都会设置一个原型,指向它的原型对象。在JS中万物皆对象。字符串String是对象,数组([])是对象,对象({})是对象,函数方法(Function)是对象。下面分别看下通常对象 和 函数对象 的原型连。
String对象的原型链:
和以前的对象xm相似字符串对象str也只有__proto__属性,没有prototype属性。
str的原型链是:
str --> String.prototype --> Object.prototype --> null

var str = 'aaaaaaaa'

//对象的__proto__ 和 prototype
console.log(str.__proto__)  //[String: '']
console.log(str.prototype)  //undefined

console.log(typeof(str.__proto__))  //object

// 原型链
console.log(str.__proto__ === String.prototype) //true
console.log(String.prototype.__proto__ === Object.prototype)    //true
console.log(Object.prototype.__proto__ === null)    //true

// 构造函数
console.log(str.constructor === String.prototype.constructor) //true
console.log(String.prototype.constructor === String)    //true
console.log(Object.prototype.constructor === Object)    //true

// 类型
console.log(typeof(String.prototype))       //object
console.log(typeof(String.prototype.__proto__)) //object
console.log(typeof(Object.prototype))       //object
console.log(typeof(Object.prototype.__proto__))     //object

函数对象的原型链,函数对象不只有__proto__,还有属性prototype
那么Function()Object()和前面的String()是函数对象,由于它们有prototype属性。
经过new Foo()构建的对象foo_object的原型链是:
foo_object --> Foo.prototype --> Object.prototype --> null
函数Foo()也是一个对象,它的原型链是:
Foo() --> Function.prototype --> Object.prototype --> nullblog

function Foo() {
    return 0;
}

var foo_object = new Foo();

// 函数对象的__proto__ 和 prototype
console.log(Foo.__proto__)  //[Function]
console.log(Foo.prototype)  //Foo {}

console.log(typeof(Foo.__proto__))  //function
console.log(typeof(Foo.prototype))  //object

// 原型链
console.log(foo_object.__proto__ === Foo.prototype);    // true
console.log(Foo.prototype.__proto__ === Object.prototype);  // true
console.log(Object.prototype.__proto__ === null)    // true

console.log(Foo.__proto__ === Function.prototype);  // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null)    // true

// 构造函数
console.log(Foo.constructor === Function.prototype.constructor)   //true
console.log(Function.prototype.constructor === Function)    //true
console.log(Object.prototype.constructor === Object)    //true

// 类型
console.log(typeof(Function.prototype))     //function
console.log(typeof(Function.prototype.__proto__))   //object
console.log(typeof(Object.prototype))       //object
console.log(typeof(Object.prototype.__proto__))     //object

1).全部对象有属性__proto__,指向对象的原型对象。
2).函数对象除了有属性__proto__,还有属性prototypeprototype属性指向(构造)函数对象共享的属性和方法(即一个由此构造函数构造而来的对象能够继承prototype指向的属性及方法),prototype属性使您有能力向对象添加属性和方法。其中有一个constructor属性指向函数对象自己(String.prototype.constructor === StringFunction.prototype.constructor === Function)。
继承

展现下如何使用 prototype 属性来向对象添加属性:
1)经过Student.prototype的方式向函数对象添加了agehello()属性,后续构建的对象jacksara都继承了所增属性。
2)hello()属性分别在sara对象构建以前和jack对象构建以后添加的,但继承获得的hello()是同样的。那么在JS中一个已构建的对象,还能够经过其原型对象“不着痕迹”的扩展属性。

function Student(name) {
    this.name = name;
}

Student.prototype.age = 23;

var jack = new Student('jack');


Student.prototype.hello = function () {
    console.log('Hello, ' + this.name + '!');
}; 

var sara = new Student('sara')

console.log(jack.age);   //23
jack.hello();   //Hello, jack!

console.log(sara.age)   //23
sara.hello()    //Hello, sara!

console.log(sara.hello === jack.hello)  //true


四、原型继承

JavaScript因为采用原型继承,咱们没法直接扩展一个Class,由于不存在Class这种类型。
Student的构造函数

function Student(props) {
    this.name = props.name || 'Unnamed';
}

Student.prototype.hello = function () {
    console.log('Hello, ' + this.name + '!');
}

若是要基于Student扩展出PrimaryStudent,能够先定义出PrimaryStudent

function PrimaryStudent(props) {
    // 调用Student构造函数,绑定this变量:
    Student.call(this, props);
    this.grade = props.grade || 1;
}

可是,调用了Student构造函数不等于继承了StudentPrimaryStudent建立的对象的原型链仍是:
new PrimaryStudent() --> PrimaryStudent.prototype --> Object.prototype --> null
要想办法把原型链修改成:
new PrimaryStudent() --> PrimaryStudent.prototype --> Student.prototype --> Object.prototype --> null
这样,原型链对了,继承关系就对了。新的基于PrimaryStudent建立的对象不但能调用PrimaryStudent.prototype定义的方法,也能够调用Student.prototype定义的方法。
可借助一个中间对象来实现正确的原型链,中间对象能够用一个空函数F来实现,原型指向Student.prototype。代码以下:

// Student构造函数:
function Student(props) {
    this.name = props.name || 'Unnamed';
}

Student.prototype.hello = function () {
    console.log('Hello, ' + this.name + '!');
}

// PrimaryStudent构造函数:
function PrimaryStudent(props) {
    Student.call(this, props);
    this.grade = props.grade || 1;
}
// 中间对象-空函数F():
function F() {
}
// step1:把F的原型指向Student.prototype:
F.prototype = Student.prototype;

// step2:把PrimaryStudent的原型指向一个新的F对象,F对象的原型正好指向Student.prototype:
PrimaryStudent.prototype = new F(); //PrimaryStudent.prototype.__proto__ === F.prototype === Student.prototype

// step3:把PrimaryStudent原型的构造函数修复为PrimaryStudent:
PrimaryStudent.prototype.constructor = PrimaryStudent;

// 继续在PrimaryStudent原型(就是new F()对象)上定义方法:
PrimaryStudent.prototype.getGrade = function () {
    return this.grade;
};

// 建立xiaoming:
var xiaoming = new PrimaryStudent({
    name: '小明',
    grade: 2
});
xiaoming.name; // '小明'
xiaoming.grade; // 2

// 验证原型链:
console.log(xiaoming.__proto__ === PrimaryStudent.prototype); // true
console.log(PrimaryStudent.prototype.__proto__ === Student.prototype); // true
console.log(Student.prototype.__proto__ === Object.prototype);  // true
// 验证继承关系:
console.log(xiaoming instanceof PrimaryStudent); // true
console.log(xiaoming instanceof Student); // true


五、class继承

新的关键字class从ES6开始正式被引入到JavaScript中。
若是用新的class关键字来编写Student,并且能够直接经过extends来实现继承,原型继承的中间对象,原型对象的构造函数等都不须要考虑了。
使用class定义的对象如Student仍是函数对象,class的目的就是让定义类更简单。class的定义包含了构造函数constructor和定义在原型对象上的函数hello()(注意没有function关键字),这样就避免了Student.prototype.hello = function () {...}这样分散的代码。

// class关键
class Student {
    constructor(name) {
        this.name = name;
    }

    hello() {
        console.log('Hello, ' + this.name + '!');
    }
}
console.log(typeof(Student));   //function

var xiaoming = new Student('小明');
xiaoming.hello();   // Hello, 小明!

class PrimaryStudent extends Student {
    constructor(name, grade) {
        super(name); // 记得用super调用父类的构造方法!
        this.grade = grade;
    }

    myGrade() {
        console.log('I am at grade ' + this.grade);
    }
}

var xiaohong = new PrimaryStudent('小红', 24) 
xiaohong.hello()    // Hello, 小红!
xiaohong.myGrade()  // I am at grade 24

ES6引入的class和原有的JavaScript原型继承有什么区别呢?实际上它们没有任何区别,class的做用就是让JavaScript引擎去实现原来须要咱们本身编写的原型链代码。简而言之,用class的好处就是极大地简化了原型链代码。

相关文章
相关标签/搜索