浅谈Javascript中的原型、原型链、继承
构造函数,原型,实例三者的关系
构造函数:
构造函数是创建对象的一种常用方式, 其他创建对象的方式还包括工厂模式, 原型模式, 对象字面量等.我们来看一个简单的构造函数:
function Product (name, role){ //构造函数的命名约定第一个字母使用大写的形式!
this.name = name;
this.role = role;
}
( 1 ) 每一个构造函数都有一个prototype
属性,我们可以在Chorme
控制台中打印出Product.prototype
属性.
( 2 ) 通过控制台打印出的结果可以发现,Product.prototype
属性其实是一个指针,指向一个对象, 该对象拥有一个constructor
属性.( __proto__
属性是浏览器引入的非标准属性,方便开发者调试用的,可以不予理会!)
( 3 ) 所以,构造函数中的prototype
属性指向一个对象, 该对象就是我们接下来要说的原型
原型
《Javascript高级程序设计》中是这样描述原型这个概念的:
无论什么时候, 只要创建一个新函数, 就会根据一组特定的规则为该函数创建一个prototype
属性, 这个属性指向函数的原型对象. 在默认情况下, 所有原型对象都会自动获得一个constructor()
构造函数属性,这个属性包含一个指向prototype
属性所在函数的指针.
我相信看完这句话的你是懵逼的,下面我来解释一下这句话:
( 1 ) 构造函数的prototype
属性所指向的对象我们将其称为原型
( 2 ) 原型这个对象中,有一个constructor
属性又指回其构造函数本身.
( 3 ) 下面的图二,可以更清晰的弄明白两者之间的关系.
实例
通过构造函数创建对象的过程我们称之为实例化,创建出来的对象称为实例.现在我们通过我们的Product
这个构造函数创建一个实例(对象).
function Product (name, role){
this.name = name;
this.role = role;
}
var p1 = new Product('apple', 'fruit');
( 1 ) 现在我们来为原型添加一个方法
Product.prototype.greet = function(){
console.log('我是一个很甜的'+ this.name +', 买我!')
}
( 2 ) 在实例中调用该方法
p1.greet(); //我是一个很甜的apple, 买我!
可以看到,我们在原型中添加的方法,同时实例是可以使用的. 所以,我们可以得出以下结论:
原型里面的所有属性和方法会连接到其对应的构造函数的实例上 !
( 3 ) 构造/实例/原型三角形
通过下面的构造/实例/原型三角形可以更好的理解三者之间的关系:
结论
- 站在构造函数角度看: 函数名.prototype这个对象是构造函数的原型属性
- 站在实例对象角度看: 函数名.prototype这个对象是实例对象的原型对象
- 原型式继承: 原型对象的属性和方法可以直接被实例对象所访问.
原型链
理解原型链
从第一部分的结论中我们知道了,实例对象可以直接访问原型对象的属性和方法. 其实原型对象本质上也是一个对象,它其实是由new Object()实例化出来的.该原型对象也可以访问Object.prototype的所有属性和方法.这样其实就构成了原型链这一个重要的概念.
代码分析
下面我们来分析以下一段代码
//创建Product构造函数
function Product (){
this.name = 'apple';
}
//给Product构造函数的原型增加方法
Product.prototype.greet = function(){
console.log('我是一个很甜的'+ this.name +', 买我!');
}
//创建SalesProduct构造函数
function SalesProduct (){
this.name = 'bad apple';
}
//将Product的实例对象直接赋给SalesProduct的原型
SalesProduct.prototype = new Product();
//给SalesProduct的原型一个方法
SalesProduct.prototype.anotherGreet = function(){
console.log('我是一个' + this.name + ',不要买我!');
}
//实例化SalesProduct
var p2 = new SalesProduct();
p2.greet(); //我是一个很甜的bad apple, 买我!
( 1 ) 在上面的代码中,我们没有使用SalesProduc
t默认提供的原型,而是将其换了一个新的原型.该新原型是Product
的实例.
( 2 ) 所以,该新原型不仅作为Product
的实例拥有其全部属性和方法, 其内部还拥有一个指针,指向Product.prototype
.我们可以从调用p2.greet()
的结果可以看出.
( 3 ) 所以,可以得出以下的链条 p2 => SalesProduct.prototype => Product.prototype => Object.prototype
( 4 ) 上面的这一个链条就形成了原型链.值得注意的是,Product.prototype
最后还指向了Object.prototype
,这也是所有的自定义对象都会具有toString()
,hasOwnProperty()
等方法的原因.
重要结论
( 1 ) 原型的顶端: Object.prototype
, 任何一个默认的内置的函数的原型都继承自Object.prototype
.
( 2 ) 原型链 : Js的对象结构中出现的指向Object.prototype
的一系列原型对象,我们称之为原型链.
( 3 ) 属性搜索原则 : 在访问对象的属性和方法时, 会在当前对象中查找, 如果没有找到, 会一直沿着原型链上的原型对象向上查找, 直到找到Object.prototype
为止
( 4 ) 写入原则 : 如果给对象设置属性和方法, 都是在当前对象上设置.
实现继承的其他方法
原型链继承是Javascript
中实现继承的主要方式,但是原型链的继承存在一个问题,就是当对象有一个引用类型的属性时,该引用类型会被其后的所有实例所共享.下面的代码可以说明这一个问题.
function TypeOne(){
this.colors = ['red', 'blue', 'green'];
}
function TypeTwo(){}
TypeTwo.prototype = new TypeOne();
var t1 = new TypeTwo();
t1.colors.push('black');
console.log(t1.colors); //["red", "blue", "green", "black"]
var t2 = new TypeTwo();
console.log(t2.colors); //["red", "blue", "green", "black"]
可以看,在t1这个实例中进行的colors
这个属性的更改在t2这个实例中反映了出来.也就是说,所有的实例对象共享同一colors
属性.
基于原型链继承的这一缺点,我们需要通过其他方式实现继承,下面来介绍几个常用的方法.
借用构造函数
在解决原型中包含引用类型值所带来问题的过程中, 开发人员开始使用一种叫借用构造函数的技术. 这种技术的基本思想相当简单, 即在子类型构造函数的内部调用超类型构造函数. 函数只不过是在特定环境中执行代码的对象, 因此, 通过使用apply()
和call()
方法可以在新创建的对象上执行构造函数,我们通过以下代码来解决上面的问题:
function TypeOne(){
this.colors = ['red', 'blue', 'green'];
}
function TypeTwo(){
TypeOne.call(this);
}
TypeTwo.prototype = new TypeOne();
var t1 = new TypeTwo();
t1.colors.push('black');
console.log(t1.colors); //["red", "blue", "green", "black"]
var t2 = new TypeTwo();
console.log(t2.colors); //["red", "blue", "green"]
混入继承
我们可以通过一个克隆函数extend()
,将一组对象中的所有属性和方法克隆到一个对象中,这种实现继承的方法我们,我们将其称为混入继承.在jQuery的源码中,大量的采用了该方法.该克隆函数代码如下:
function extend (des, objs){
for(var i = 0; i < objs.length; i++){
for(var key in objs[i]){
if(objs[i].hasOwnProperty(key)){
des[key] = objs[i][key];
}
}
}
}
下面,我们通过该克隆函数实现三个对象中属性和方法继承到一个对象上:
var o1 = {
name : 'Lee_Tanghui'
}
var o2 = {
sayHi: function (){
console.log('你好,我是' + this.name);
}
}
var o3 = {
introduce: function (){
console.log('我来自重庆');
}
}
var obj = {};
//调用该方法实现让obj继承o1,o2,o3
extend(obj, [o1, o2, o3]);
console.log(obj.name); //Lee_Tanghui
obj.sayHi(); //你好,我是Lee_Tanghui
obj.introduce(); //我来自重庆
从上面的结果可以看到,obj对象继承了来o1,o2,o3的所有方法和属性.
组合继承
组合继承,指的是将原型链和借用构造函数的技术组合到一块. 从而发挥二者之长的一种继承模式. 其背后的思路是使用原型链实现对原型属性和方法的继承, 而通过借用构造函数来实现对实例属性的继承. 这样, 既通过在原型上定义方法实现了函数复用, 又能保证每个实例都有它自己的属性.我们可以通过以下代码来了解组合继承:
function TypeOne(name){
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
TypeOne.prototype.sayName = function(){
console.log(this.name);
}
function TypeTwo(name, age){
TypeOne.call(this, name); //通过构造函数实现对实例属性的继承
this.age = age;
}
TypeTwo.prototype = new TypeOne(); //通过原型链实现对原型属性和方法的继承
TypeTwo.prototype.sayAge = function() {
console.log(this.age);
}
var t1 = new TypeTwo('Lee_Tanghui', 23);
t1.colors.push('black');
console.log(t1.colors); //["red", "blue", "green", "black"]
t1.sayName(); //Lee_Tanghui
t1.sayAge(); //23
var t2 = new TypeTwo('Joe', 24);
console.log(t2.colors); //["red", "blue", "green"]
t2.sayName(); //Joe
t2.sayAge(); //24
原型式继承
原型式继承是借助一个函数,通过传递原型对象作为参数,从而创建新对象的方式实现继承.该函数由道格拉斯提出,代码源码如下:
function object(o) {
function F(){};
F.prototype = o;
return new F();
}
在ECMAScript 5中新增加了Object.create()
方法规范化了原型式继承.我们可以通过该方法实现将一下对象
var person = {
name : 'joe',
age : 24
}
变为
[{key: "name", value: "joe" }, {key: "age", value: 24}]的数组的形式
代码实现如下
var person = {
name : 'joe',
age : 24,
}
//创建构造函数,用来排列键值对
function SortedList ( obj ) {
for ( var key in obj ) {
this.push({
key : key,
value : obj[ key ]
})
}
}
//调用Object.create()方法,继承数组的所有属性方法
SortedList.prototype = Object.create( Array.prototype );
//构造函数的实例化
var list = new SortedList( person );
console.log( list ); //[{key: "name", value: "joe" }, {key: "age", value: 24}]
浅谈Javascript中的原型、原型链、继承的更多相关文章
- 浅谈JavaScript中的闭包
浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...
- 浅谈JavaScript中的null和undefined
浅谈JavaScript中的null和undefined null null是JavaScript中的关键字,表示一个特殊值,常用来描述"空值". 对null进行typeof类型运 ...
- 浅谈JavaScript中的正则表达式(适用初学者观看)
浅谈JavaScript中的正则表达式 1.什么是正则表达式(RegExp)? 官方定义: 正则表达式是一种特殊的字符串模式,用于匹配一组字符串,就好比用模具做产品,而正则就是这个模具,定义一种规则去 ...
- 浅谈JavaScript中的继承
引言 在JavaScript中,实现继承的主要方式是通过原型链技术.这一篇文章我们就通过介绍JavaScript中实现继承的几种方式来慢慢领会JavaScript中继承实现的点点滴滴. 原型链介绍 原 ...
- 浅谈JavaScript中的原型模式
在JavaScript中创建对象由很多种方式,如工厂模式.构造函数模式.原型模式等: <pre name="code" class="html">/ ...
- 浅谈JavaScript中的变量、参数、作用域和作用域链
基本类型和引用类型 在JavaScript中有两种数据类型值.基本类型值和引用类型值.基本类型值指的是简单的数据段,而引用类型值指的是可能由多个值构成的对象.在JavaScript中有5种基本数据类型 ...
- 浅谈JavaScript中继承的实现
谈到js中的面向对象编程,都有一个共同点,选择原型属性还是构造函数,两者各有利弊,而就片面的从js的对象创建以及继承的实现两个方面来说,官方所推荐的是两个相结合,各尽其责,各取其长,在前面的例子中,我 ...
- 通过一道笔试题浅谈javascript中的promise对象
因为前几天做了一个promise对象捕获错误的面试题目,所以这几天又重温了一下promise对象.现在借这道题来分享下一些很基础的知识点. 下面是一个面试题目,三个promise对象捕获错误的例子,返 ...
- 浅谈JavaScript中的this
引言 JavaScript 是一种脚本语言,因此被很多人认为是简单易学的.然而情况恰恰相反,JavaScript 支持函数式编程.闭包.基于原型的继承等高级功能.本文仅采撷其中的一例:JavaScri ...
随机推荐
- iOS开发tip-图片方向
概述 相信稍微接触过iOS图片相关操作的同学都遇到过图片旋转的问题,另外使用AVFoundation进行拍照的话就会遇到前后摄像头切换mirror问题就让人更摸不着头脑了.今天就简单和大家聊一下iOS ...
- keras冒bug
使用keras做vgg16的迁移学习实验,在实现的过程中,冒各种奇怪的bug,甚至剪贴复制还是出问题. 解决方案: 当使用组合keras和tensorflow.keras时.由于版本不一致问题导致很多 ...
- re模块的使用
re模块下的函数 compile(pattern):创建模式对象 import re pat = re.compile('D') m = pat.search('CBA') #等价于re.search ...
- mysql--->innodb引擎什么时候表锁什么时候行锁?
mysql innodb引擎什么时候表锁什么时候行锁? InnoDB基于索引的行锁 InnoDB行锁是通过索引上的索引项来实现的,这一点MySQL与Oracle不同,后者是通过在数据中对相应数据行加锁 ...
- CUDA学习(七)之使用CUDA内置API计时
问题:对于使用GPU计算时,都想知道kernel函数运行所耗费的时间,使用CUDA内置的API可以方便准确的获得kernel运行时间. 在CPU上,可以使用clock()函数和GetTickCount ...
- 1751: n个素数构成等差数列
#include <stdio.h>int fill(char *map,int *primes) { for (int i = 2; i < 1001; i++) { map[i] ...
- f(n)=1-1/2+1/3-1/4...+1/n
#include <stdio.h>//f(n)=1+1/1+1/2+1/3+...+1/n int main(){ int n,i; double sum=0.0; scanf(&quo ...
- 【大白话系列】MySQL 学习总结 之 初步了解 MySQL Server 的 binlog 组件
一.上节回顾 上节我们讲到,建议将 redo log 的刷盘策略设置为1:即提交事务时,强制将 redo log buffer 里的 redo log 刷入到磁盘后才算事务提交成功. 但是我们都知道, ...
- Markdown 教程
Markdown 简介 Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档. Markdown 语言在 2004 由约翰·格鲁伯(英语:John Gruber)创建. ...
- 第一次安装android studio遇到的问题
安装android studio一点都不顺利,最后总是成功安装,但是忘了把问题记录下来,下一次肯定还肯能出现问题 忘了把问题记录下来了,好像是sync failed 第一次安装3.1.4遇到的问题,好 ...