理解javascript中的对象

以前对javascript中的对象总有不明白的地方,在本周也遇到了疑惑,于是借着机会去深入的了解了一下javascrpit中的对象。

创建对象

在javascript中,创建对象有两种方式,一种是使用 new 操作符后跟 Object 构造函数:

let ob = new Object();
ob.name = 'object';

另一种是直接使用对象字面值:

let ob = {
      name: 'object'
    };

这两种方式是等价的,虽然 Object 构造函数或对象字面量都可以用来创建单个对象,但这些方式有个明显的缺点:使用同
一个接口创建很多对象,会产生大量的重复代码。在面向对象的设计语言中,通常通过创建类来解决这一问题。

创建类

我们可以通过自定义构造函数来实现javascript中的类:

function Student(name) {
    this.type = 'student';
    this.name = name;

    this.sayName = function() {
        console.log(this.name);
    }
}

let student1 = new Student('zhangsan');
student1.sayName(); // zhangsan

let student2 = new Student('lisi');
student2.sayName(); // lisi

要创建 Person 的新实例,必须使用 new 操作符。以这种方式调用构造函数实际上会经历以下 4个步骤:
(1) 创建一个新对象;
(2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
(3) 执行构造函数中的代码(为这个新对象添加属性);
(4) 返回新对象。

这样一来,在创建相同类别的对象时,我们就不需要写大量重复的代码。但是使用构造函数创建对象并非没有缺点。使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍,并且也无法做到实例之间共享变量。

console.log(student1.sayName === student2.sayName); // false 不是同一个方法实例

为方法创建两个实例却是没有什么必要,因为在this的情况下,根本不需要在执行代码前就把函数绑定到特定对象上面。好在javascript也有解决的办法。

我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。
理解javascript中的对象_第1张图片

修改我们的代码,将对象的方法和共享变量放置到原型对象上:

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

Student.prototype.sayName = function() {
    console.log(this.name);
}
Student.prototype.type='student';

let student1 = new Student('zhangsan');
student1.sayName();    // zhangsan

let student2 = new Student('lisi');
student2.sayName();    // lisi

console.log(student1.sayName === student2.sayName);    // true
console.log(student1.type);    // student

当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262 第 5 版中管这个指针叫[[Prototype]]。这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。

理解javascript中的对象_第2张图片

每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。这样一来,我们就可以通过原型对象来实现实例之间共享方法和变量了。

继承

继承是面向对象语言中十分重要的一个概念。javascript 实现继承主要是依靠原型链来实现的。

ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。

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

Student.prototype.sayName = function() {
    console.log(this.name);
}
Student.prototype.type='student';

function CollegeStudent() {
    this.major = '计算机';
    Student.call(this,'wangwu');
}

CollegeStudent.prototype = new Student();


let collegeStudent = new CollegeStudent();

collegeStudent.sayName();

console.log(collegeStudent);

以上代码,我们用大学生继承了学生,使用Student.call(this,'wangwu')为CollegeStudent中的this作用域添加上name属性,再将CollegeStudent的原型对象设为new Student(),这样一来,就可以通过CollegeStudent的原型对象找到Student对象上的方法和属性了,这就是原型链。

理解javascript中的对象_第3张图片

虽然通过此方式可以完成继承的功能,但是无论什么情况下,都会调用两次Student构造函数。第一次是在使用Student.call()时,第二次是在使用CollegeStudent.prototype = new Student()时。我们在CollegeStudent原型中并不需要Student的属性,我们只需要Student的原型,因此可以改进为:

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

Student.prototype.sayName = function() {
    console.log(this.name);
}
Student.prototype.type='student';

function CollegeStudent() {
    this.major = '计算机';
    Student.call(this,'wangwu');
}

CollegeStudent.prototype = create(Student);
let collegeStudent = new CollegeStudent();

collegeStudent.sayName();

function create(father) {
    function f() {
    }
    f.prototype = father.prototype;
    return new f();
}

唯一的不同点就在于CollegeStudent.prototype = create(Student),此时CollegeStudent的原型对象指向的是仅仅包含Student的原型对象而不包含Student的实例属性(在f函数中并没有定义任何的属性,仅仅将f的原型对象指向Student的原型对象,此时new f()得到的实例对象仅仅包含对Student原型对象的引用)。
此时的CollegeStudent原型链:
理解javascript中的对象_第4张图片

你可能感兴趣的:(javascript)