之前对javascript中的对象总有不明白的地方,在本周也遇到了疑惑,因而借着机会去深刻的了解了一下javascrpit中的对象。javascript
在javascript中,建立对象有两种方式,一种是使用 new 操做符后跟 Object 构造函数:java
let ob = new Object(); ob.name = 'object';
另外一种是直接使用对象字面值:函数
let ob = { name: 'object' };
这两种方式是等价的,虽然 Object 构造函数或对象字面量均可以用来建立单个对象,但这些方式有个明显的缺点:使用同
一个接口建立不少对象,会产生大量的重复代码。在面向对象的设计语言中,一般经过建立类来解决这一问题。this
咱们能够经过自定义构造函数来实现javascript中的类:spa
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) 返回新对象。prototype
这样一来,在建立相同类别的对象时,咱们就不须要写大量重复的代码。可是使用构造函数建立对象并不是没有缺点。使用构造函数的主要问题,就是每一个方法都要在每一个实例上从新建立一遍,而且也没法作到实例之间共享变量。设计
console.log(student1.sayName === student2.sayName); // false 不是同一个方法实例
为方法建立两个实例倒是没有什么必要,由于在this的状况下,根本不须要在执行代码前就把函数绑定到特定对象上面。好在javascript也有解决的办法。指针
咱们建立的每一个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含能够由特定类型的全部实例共享的属性和方法。code
不管何时,只要建立了一个新函数,就会根据一组特定的规则为该函数建立一个 prototype属性,这个属性指向函数的原型对象。在默认状况下,全部原型对象都会自动得到一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。对象
修改咱们的代码,将对象的方法和共享变量放置到原型对象上:
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 实现继承主要是依靠原型链来实现的。
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对象上的方法和属性了,这就是原型链。
虽然经过此方式能够完成继承的功能,可是不管什么状况下,都会调用两次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原型链: