原型和原型链

一、构造函数

构造函数就是一个普通的函数,创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写。另外就是调用方式的不同,普通函数是直接调用,而构造函数需要使用new关键字来调用。

function Person(name, age, gender) {
        this.name = name
        this.age = age
        this.gender = gender
        this.sayName = function () {
            alert(this.name);
        }
    }
    var per = new Person("孙悟空", 18, "男");
    function Dog(name, age, gender) {
        this.name = name
        this.age = age
        this.gender = gender
    }
    var dog = new Dog("旺财", 4, "雄")
    console.log(per);//当我们直接在页面中打印一个对象时,事件上是输出的对象的toString()方法的返回值
    console.log(dog);

原型对象只存在于函数对象。也就是本质上只要是通过new Function创建的函数对象会有一个原型对象。
而对于其他非Function的引用类型归根结底也是通过new Function创建的。

如上面提到的Array类型、Object类型。

实际上,在每个函数对象创建的时候,都会自带一个prototype的属性,这个属性相当于一个指针,指向他本身的原型对象,这个原型对象里包含着自定义的方法属性,

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

在默认情况下,a.prototype下会带有一个constructor属性,这个属性指向创建当前函数对象的构造函数,比如这里
constructor指向构造函数a本身,也就是说:

a.prototypr.constructor==a   //true

另外默认还有一个_proto_属性,这个属性指向由创建这个函数对象的引用类型中继承而来的属性和方法。

当通过构造函数实例化一个对象a时,即 new a();
首先这个new出来的对象属于普通对象,所以没有prototype属性。但他有_proto_这个属性,这个属性指向创建它的引用类型的原型对象,在这个例子中指向a.prototype,从而继承来自引用类型a的属性和方法。
var 对象 = new 函数对象这个声明形式可以引申出:

函数.__proto__ ===Function.prototype
Function.__proto__ === Function.prototype
Object.__proto__ === Function.prototype //Objec也是个函数,函数都是由Function构造出来的。
Number.__proto__ === Function.prototype
构造函数.prototype.__proto__ ===Object.prototype
Function.prototype.__proto__ ===Object.prototype
Number.prototype.__proto__ ===Object.prototype
Object.__proto__ .__proto__ ===null

理解了以上的关系后,’__proto__‘是对象的属性、’prototype'是函数的属性这句话也就懂了
在这里插入图片描述
在JS中,undefined是全局对象的一个属性,它的初始值就是原始数据类型undefined,并且无法被配置,也无法被改变。undefined从字面意思上理解为“未定义”,即表示一个变量没有定义其值。

null是一个JS字面量,表示空值,即没有对象。与undefined相比,null被认为是“期望一个对象,但是不引用任何对象的值”,而undefined是纯粹的“没有值”。
《undefined与null的区别》
// null为对象原型链的终点 console.log(Object.getPrototypeOf(Object.prototype)); // null // null是一个对象 console.log(typeof null); // object // null 为空 console.log(!null); // true

JS中的所有事物都是对象,对象是拥有属性和方法的数据。

为了描述这些事物,JS便有了“原型(prototype)”的概念。

原型模式是js对继承的一种实现:使用原型,能复用代码,节省内存空间 (java类的代码在内存只有一份,然后每个对象执行方法都是引用类的代码,所有子类对象调用父类方法的时候,执行的代码都是同一份父类的方法代码。但是JS没有类,属性和方法都是存在对象之中,根本没有办法做到java那样通过类把代码共享给所有对象)。

推荐阅读《深刻理解JavaScript基于原型的面向对象

从一张图看懂原型对象、构造函数、实例对象之间的关系
在这里插入图片描述
prototype:构造函数中的属性,指向该构造函数的原型对象。

constructor:原型对象中的属性,指向该原型对象的构造函数

_proto_:实例中的属性,指向new这个实例的构造函数的原型对象
在这里插入图片描述

在JavaScript 中,每个对象都有一个指向它的原型(prototype)对象的内部链接。这个原型对象又有自己的原型,直到某个对象的原型为 null 为止(也就是不再有原型指向),组成这条链的最后一环。这种一级一级的链结构就称为原型链(prototype chain)。

要清楚原型链,首先要弄清楚对象

普通对象

最普通的对象:有__proto__属性(指向其原型链),没有prototype属性。

原型对象(Person.prototype 原型对象还有constructor属性(指向构造函数对象))

函数对象:

凡是通过new Function()创建的都是函数对象。

拥有__proto__、prototype属性(指向原型对象)。

JavaScript 对象是动态的属性“包”(指其自己的属性)。JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依此层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
在这里插入图片描述
原型-显式原型-隐式原型-共享原型链

显式原型(explicit prototype property)每一个函数在创建之后都会拥有一个名为prototype的属性,这个属性指向函数的原型对象。用来实现基于原型的继承与属性的共享。

隐式原型 (implicit prototype link)JS中任意对象都有一个内置属性__proto__(部分浏览器为[[prototype]]),指向创建这个对象的函数(即构造函数)constructor的prototype。用来构成原型链,同样用于实现基于原型的继承。
在这里插入图片描述
当我们「读取」 obj.toString 时,JS 引擎会做下面的事情:

  1. 看看 obj 对象本身有没有 toString 属性。没有就走到下一步。

  2. 看看 obj.__proto__ 对象有没有 toString 属性,发现 obj.__proto__ 有 toString 属性,于是找到了

  3. 如果 obj.__proto__ 没有,那么浏览器会继续查看 obj.__proto__.__proto__,如果 obj.__proto__.__proto__ 也没有,那么浏览器会继续查,obj.__proto__.__proto__.proto__

直到找到 toString 或者 __proto__ 为 null(不管你从那个属性开始,连续引用__proto__的指针,最后输出的那个值就是null)。

上面的过程,就是「读」属性的「搜索过程」。

而这个「搜索过程」,是连着由 __proto__ 组成的链子一直走的。

这个链子,就叫做「原型链」。

要搞清楚 valueOf / toString / constructor 是怎么来的,就要用到 console.dir 了。
在这里插入图片描述
共享原型链(Shared prototype chain)此模式所有子对象及后代对象都共享一个原型(都是通过b.prototype=a.prototype;这种模式连接的对象),在这些后代对象上修改原型,会影响所以处在同一共享原型链上的所有对象。而且此模式只继承原型链上的属性和方法,通过this定义的属性和方法无法访问和继承

在这里插入图片描述
那么 obj.toString 和 obj2.toString 其实是同一个东西,也就是 obj2.__proto__.toString

这有什么意义呢?

如果我们改写 obj2.__proto__.toString,那么 obj.toString 其实也会变!

这样 obj 和 obj2 就是具有某些相同行为的对象,这就是意义所在。

如果我们想让 obj.toString 和 obj2.toString 的行为不同怎么做呢?

直接赋值就好了:

obj.toString = function(){ return ‘新的 toString 方法’ }

原型对象

每创建一个函数都会有一个prototype属性,这个属性是一个指针,指向一个对象(通过该构造函数创建实例对象的原型对象)。原型对象是包含特定类型的所有实例共享的属性和方法。原型对象的好处是,可以让所有实例对象共享它所包含的属性和方法。

原型对象属于普通对象。Function.prototype是个例外,它是原型对象,却又是函数对象,作为一个函数对象,它又没有prototype属性。
在这里插入图片描述

对象与函数

拥有了描述事物的能力,却没有创造事物的能力,显然是不完整的,因此需要一个Object的生成器来进行对象的生成。

JS将生成器以构造函数constructor来表示,构造函数是一个指针,指向了一个函数。

函数(function) 函数是指一段在一起的、可以做某一件事的程序。构造函数是一种创建对象时使用的特殊函数。

在这里插入图片描述
对象的构造函数function Object同时也是一个对象,因此需要一个能够描述该对象的原型,该原型便是Function.prototype,函数的原型用来描述所有的函数。对象的构造函数的__proto__指向该原型。
在这里插入图片描述
https://upload-images.jianshu.io/upload_images/1048493-1c2e5e6012e0e6c4.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/720/format/webp
函数的原型本身也是对象,因此其__proto__指向了对象的原型。同样,该对象也需要一个对应的生成器,即其构造函数function Function。

在这里插入图片描述
函数的构造函数是由函数生成的一个对象,所以其原型即为函数的原型,其隐式原型也同样为函数的原型Function.prototype。

instanceof操作符的内部实现机制和隐式原型、显式原型有直接的关系。instanceof的左值一般是一个对象,右值一般是一个构造函数,用来判断左值是否是右值的实例。它的实现原理是沿着左值的__proto__一直寻找到原型链的末端,直到其等于右值的prototype为止。

instanceof 的作用是判断一个对象是不是一个函数的实例。比如 obj instanceof fn, 实际上是判断fn的prototype是不是在obj的原型链上。所以

instanceof运算符的实质:用来检测 constructor.prototype是否存在于参数 object的原型链上。

根据上图展示的Object和Function的继承依赖关系,我们可以通过instanceof操作符来看一下Object和Function的关系:

console.log(Object instanceof Object); // true console.log(Object instanceof Function); // true console.log(Function instanceof Object); // true console.log(Function instanceof Function); // true

函数与对象相互依存,分别定义了事物的描述方法和事物的生成方法,在生成JS万物的过程中缺一不可。

Function instanceof Function // true, why? Function.prototype是原型对象,却是函数对象

Object特殊在Object.prototype是凭空出来的。语法上,所有的{}都会被解释为new Object();

Function特殊在__proto__ == prototype。语法上,所有的函数声明都会被解释为new Function()。

我们来看Function和Object的特殊之处:

Object是由Function创建的:因为Object.proto === Funciton.prototype;

同理,Function.prototype是由Object.prototype创建的;

Funciton是由Function自己创建的!

Object.prototype是凭空出来的!

推荐阅读 《JavaScript 内置对象与原型链结构》与《JavaScript中的难点之原型和原型链》

这几句话能解释一切关于原型方面的问题:

当 new 一个函数的时候会创建一个对象,『函数.prototype』 等于 『被创建对象.__proto__』

一切函数都是由 Function 这个函数创建的,所以『Function.prototype === 被创建的函数.__proto__』

一切函数的原型对象都是由 Object 这个函数创建的,所以『Object.prototype === 一切函数.prototype.__proto__』

推荐阅读:《对原型、原型链、 Function、Object 的理解》

原型链是实现继承的主要方法

先说一下继承,许多OO语言都支持两张继承方式:接口继承、实现继承。

|- 接口继承:只继承方法签名

|- 实现继承:继承实际的方法

由于函数没有签名,在ECMAScript中无法实现接口继承,只支持实现继承,而实现继承主要是依靠原型链来实现。

原型链基本思路:

利用原型让一个引用类型继承另一个引用类型的属性和方法。

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数想指针(constructor),而实例对象都包含一个指向原型对象的内部指针(__proto__)。如果让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针(__proto__),另一个原型也包含着一个指向另一个构造函数的指针(constructor)。假如另一个原型又是另一个类型的实例……这就构成了实例与原型的链条。

原型链基本思路(图解):

在这里插入图片描述
推荐阅读《JS重点整理之JS原型链彻底搞清楚》

类-对象冒充-class

类(Class)是面向对象程序设计(OOP,Object-Oriented Programming)实现信息封装的基础。类是一种用户定义类型,也称类类型。每个类包含数据说明和一组操作数据或传递消息的函数。类的实例称为对象。

在ECMAScript 2015 中引入的JS类(classes)之前,要在JS中实现类便是采用原型继承的方式。

当把一个函数作为构造函数,使用new关键字来创建对象时,便可以把该函数看作是一个类,创建出来的对象则是该类的实例,其隐式原型__proto__指向的是该构造函数的原型。

在访问该对象的属性或方法时,JS会先搜索该对象中是否定义了该属性或方法,若没有定义,则会回溯到其__proto__指向的原型对象去搜索,若仍然未搜索到,则会继续回溯该原型的原型,直到搜索到原型链的终点null;

这种特性可以理解为:构造函数生成的实例,继承于该构造函数的原型。

得益于这种特性,我们可以使用定义构造函数的方式来定义类。

在这里插入图片描述
function Person() {} // 定义Person构造函数 // 通常以大写字母开头来定义类名 console.log(new Person() instanceof Person); // true

以上定义了Person类,该构造函数是由Function构造而来,所以其隐式原型指向函数的原型,而为了描述该事物,同时生成了该类的原型Person.prototype,该原型又是由Object构造而来,所以其隐式原型指向了对象的原型。

后记:文字有点乱,就是多篇文章的精华提炼。发现把一个自己懂的事情,深入浅出讲明白,并非易事。文有不妥之处,请留言告知,谢谢。

文章首发于:https://www.zhoulujun.cn/html/webfront/ECMAScript/js/2015_0715_119.html​,如果不妥之处,请到官网留言,谢谢!

作者:zhoulujun
链接:https://www.jianshu.com/p/3d03f3e83cf5
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值