JS中对象相关的最重要的恐怕就是原型链了

原型链也是JS中对象继承的实现的基础

接昨天的文章,我们使用构造函数创建对象的时候仍然存在一些问题

那就是所有的实例没法共用一个函数

这样无疑会造成极大的内存浪费

那么解决的办法是什么呢?

那就是通过对象的原型来实现

原型链

在JS中我们创建的每一个函数都有一个 prototype(原型)属性

该属性是一个指针指向一个对象,这个对象的用途是包含由特定类型所有实例的共享属性和方法

这个对象也就是我们常说的原型对象

原型链就是对象实例和对象的原型对象组成的继承链

而JS中所有的对象都继承自Object类,所以原型链的顶端也就是Object构造函数的prototype 指向的对象

可以参考下图,可能画的有点丑请多见谅

对于这样一个继承链,在其它的面向对象的语言中,最下面的有关对象的部分应该是公有的,和类的概念十分类似

而不太好理解的,就是有关构造函数的那一部分

造成JS继承灵活多变的地方就在于这里,JS中的函数也是一个对象

所以函数也有自己的原型链,为了让构造函数和被构造函数创建的实例之间联系起来,所以函数除了有自己的 [[Prototype]] 属性之外,还有prototype属性指向被其构造的对象实例的原型对象

与此同时被函数prototype属性指向的对象,其constructor属性也会指向这个函数,这样就使构造函数和对象紧密地结合在一起

PS. 上文中的 [[Prototype]] 属性,就是对象的__proto__属性,但是这一属性只在浏览器中实现,JS的其他实现,对象不一定会有 __proto__属性

在这些没有__proto__ 的实现中虽然无法访问,但是可以通过 isPrototypeOf() 来检测对象之间的这种联系

而在ES5以上的脚本实现中,还支持一个Object.getPrototypeOf() 用于获取一个对象的原型对象

原型模式

在此基础上,我们可以发现,一个构造函数构造的所有函数的实例都有办法能够访问到原型对象

那么一类对象的共享属性的存放位置就显而易见了,那就是将其存放到原型对象上

以昨天的例子来说,使用原型模式创建对象如下:

function Person(){}

var pPrototype = Person.prototype;
pPrototype.name = 'lhy';
pPrototype.job = 'font end';
pPrototype.age = 21;
pPrototype.sayName = function(){
alert(this.name);
} var person1 = new Person();

能够访问的原因在于,当代码读取对象的属性的时候 ,都会执行一次搜索,该搜索过程会从该对象实例开始

如果没有找到指定属性,则顺着原型链进行查找,直到Object.prototype为止

找到相应属性则返回,没找到则报未定义的错误

PS. 虽然我们可以通过实例对象访问原型对象的属性,但是我们无法通过实例对象来重写原型对象的属性

什么意思呢?就是说虽然我们可以通过实例对象的属性访问原型对象的某个属性,但是我们对实例对象对应属性的修改不会映射到原型对象上

如果我们在实例对象上设置了,一个本来只有原型对象上才有的属性,那么JS会为实例对象创建一个同名属性,并设置它的值

然后原型对象上的属性就被同名的属性屏蔽了,再也无法访问到原型对象上的这个属性,除非使用delete删除实例上的该属性

例如:

为了能够判断一个属性是来自于该实例,还是其原型对象

可以使用 hasOwnProperty() 方法来进行判断,对于来自实例的属性返回 true

Tips. in操作符不在for-in循环中单独使用的时候,用于判断对象上是否存在该属性,上面的方法与in操作符配合就可以判断属性到底来自实例,还是原型

function test(propertyName,obj){
if(obj.hasOwnProperty(propertyName)){
alert('来自实例');
)else if(propertyName in obj){
alert('来自原型');
}
}

Ps.  for-in 循环会遍历对象上的所有可枚举属性,包括来自原型的属性,需要注意的是,如果原型上有个不可枚举的属性,而实例上有个与之同名的可枚举属性,那么实例上的这个属性会被遍历出来,不会受原型的影响

除此而外JS提供了Object.keys() 方法,可以获取到对象所有可枚举属性的集合

原型模式还有另一种简化写法

function Person (){}
Person.prototype = {name:"lhy",age:"21"};

但是这种写法存在一个问题,由于这里直接将一个对象指定为函数的原型对象

所以这里的函数原型的 constructor 属性也被改变了,这样我们就没法通过 constructor 来判断函数类型

那怎么办呢?

办法还是有的,那就是手动将要指定的原型对象的 constructor 设置为构造函数

不过要注意的是如果手动设置,那么该属性会被for-in 遍历出来,因为这种方式的就是为实例 创建一个 constructor 属性,以此来屏蔽不正确的constructor属性

所以最后还要将 constructor 属性特性描述符的 Enumerable 设为false,才能完美地解决

原型的动态性

从之前举的例子,和刚才简化的写法,我们不难发现原型是一个动态地对象

因为我们对原型对象的修改都会立即体现在实例对象上

同样的我们在操作原型时就要更加地小心,尤其是在重写原型对象时

拿上方的例子来说,虽然我们通过一些操作尽量减少了重写原型对象带来的差异,但还有一些是没法消除的

那就是我们重写原型对象时,会切断已有实例和重写的原型对象的实例,因为它们仍旧指向旧的原型对象

讲了这么多,那么原型模式是我们创建一类对象的最好的选择嘛?

虽然对于函数来说十分地合适,但对于一些引用类型的值可能就会有一些问题

比如我希望对象拥有自己独立的属性,那么原型模式可能就无法胜任了

那么怎么办呢?

构造函数与原型的组合模式

构造函数的缺点是没法共用数据,原型模式的缺点是没法有独立数据

那将他们组合起来不就行了吗

所以这种组合模式就产生了

function Person(name, job, age){
this.name = name;
this.age = age;
this.job = job;
} Person.prototype.sayName = function(){
alert(this.name);
}

这种模式基本上就能满足我们平时的所有需求,所以这也是我们广泛使用的方式

动态原型模式

上面的方法虽然从功能上说,无可挑剔,但是暴露在外的独立的原型属性的声明,让追求极致的程序员们十分难受

所以动态原型模式就出现了

function Person(name, job, age){
this.name = name;
this.age = age;
this.job = job;
if(this.sayName !== 'function'){
Person.prototype.sayName = function(){
alert(this.name);
}
}
}

寄生构造函数模式

虽然动态原型模式,已经能够完美地处理绝大部分情况,但是有些特殊情况,仍会存在问题

就像我们不建议给原生的对象的原型添加属性,但有时候我们确实需要根据某个原生的对象来创建一类对象,那么怎么办呢?

所以就有了寄生构造函数模式,其原理就是我不在原生的原型上添加,但我可以使用工厂方法,对其构造的原型对象进行加工,从而得到我想要的对象

function specialArray(){
var values = new Array();
values.push.apply(values,arguments);
values.myName = function(){
return 'lhy';
}
return values;
}

这种只是改良了的工厂方法,存在的弊端和工厂方法几乎一致,无法确定对象类型等

所以使用场景很少

稳妥构造函数

我们知道在一些安全环境下,我们没法使用this、new等来创建,或者需要防止数据被其他应用程序篡改时使用

pS. 不使用new 是指不用new调用构造函数

function Person(name, job, age ){
var o = new Object();
o.sayName = function(){
alert(name);
}
return o;
}

这样创建对象时传入的原始数据,只能通过返回的对象o 上面的方法进行访问,所以保证了原始数据的安全性

总的来说,创建一类函数使用组合模式的场景更为广泛,使用频率也更高

Javascript高级编程学习笔记(21)—— 对象原型的更多相关文章

  1. Javascript高级编程学习笔记(20)—— 创建对象

    由于今天有点事,加上对象原型链的东西有点多,所以今天这篇就讲一个小的知识点吧 也算为明天的对象继承做铺垫 工厂模式 虽然使用对象字面量来创建一个对象十分地便捷,但是这个方法有一个显著的缺点 那就是如果 ...

  2. Javascript高级编程学习笔记(22)—— 对象继承

    继承是所有面向对象的语言最让人津津乐道的概念 许多面向对象的语言都支持两种实现继承的方式: 1.接口继承 2.实现继承 由于ECMAScript中没有函数签名,所以自然也是不支持接口继承 所以JS中能 ...

  3. Javascript高级编程学习笔记(18)—— 引用类型(7)单体内置对象

    什么是内置对象呢? js高级程序设计中给出的定义为:由ES规定不依赖于宿主环境的对象,这些对象在JS执行前就已经存在 前面我们介绍的引用类型都是内置对象 除了这些对象外ECMA还规定了两个单体内置对象 ...

  4. Javascript高级编程学习笔记(59)—— 事件(3)事件对象

    事件对象 在触发DOM‘事件时,会产生一个事件对象 event 该对象包含着所有与事件有关的信息 所有浏览器都支持 event 对象但是支持的方式有所不同 DOM事件对象 兼容DOM的浏览器会将eve ...

  5. Javascript高级编程学习笔记(31)—— BOM(5)screen、history对象

    screen对象 screen对象应该是BOM对象中最不常用的对象了 其主要用于提供客户端的显示能力信息 包括浏览器外部显示的信息,和像素的宽高等 这个对象的主要用于检测客户端能力,一般不会影响功能 ...

  6. Javascript高级编程学习笔记(30)—— BOM(4)navigator对象

    window对象作为浏览器的全局对象.location对象保存了页面的url信息 那么navigator对象又有什么作用呢? navigator对象 该对象最早由 Netspace Navigator ...

  7. Javascript高级编程学习笔记(29)—— BOM(3)location对象

    在JS中location是一个神奇的对象 它既是window对象的属性,也是document对象的属性 它的作用主要在于保存当前文档页面的信息,以及将 url 解析为独立的片段 location对象属 ...

  8. Javascript高级编程学习笔记(28)—— BOM(2)window对象2

    今天讲一下window对象和浏览器导航,弹窗等有关的内容 导航和打开窗口 window.open() 用于导航到某个特定 url 该方法接收四个参数 1.url 2.窗口目标(当页面中有多个框架fra ...

  9. Javascript高级编程学习笔记(27)—— BOM(1)window对象1

    ECMAScript是JS的核心 但是对于在浏览器中运行的JS,BOM显然才是真正的核心 我们知道JS是由三个部分组成的 BOM.DOM.ECMAScript 之前的文章我们主要介绍的是ECMAScr ...

随机推荐

  1. 【CSS】Sticky Footer 布局

    什么是 Sticky Footer 布局? Sticky Footer 布局是一种将 footer 吸附在底部的CSS布局. footer 可以是任意的元素,该布局会形成一种当内容不足,footer ...

  2. 路飞ORM练习

    # a.查看所有学位课并打印学位课名称以及授课老师 # degree_list = DegreeCourse.objects.all().values('name', 'teachers__name' ...

  3. 过滤器(Filter)与拦截器(Interceptor )区别

    目录 过滤器(Filter) 拦截器(Interceptor) 拦截器(Interceptor)和过滤器(Filter)的区别 拦截器(Interceptor)和过滤器(Filter)的执行顺序 拦截 ...

  4. cookie设置和清除,解决跨目录读取不到cookie值

    cookies.setCookie("UserType", result.UserType, null, '/'); cookies.deleteCookie("User ...

  5. Appium+Python自动化 4 -appium元素定位

    appium定位app上的元素方式 在前面文章中有介绍,(通过UIautomator工具查看app元素) appium定位app上的元素,可以通过id,name,class这些属性定位到 1.id 定 ...

  6. H5canvas画类似心电图

    HTML5 的 canvas 元素使用 JavaScript 在网页上绘制图像,我们可以使用canvas来绘制类似心电图的东西. 效果图如下: <!DOCTYPE html> <ht ...

  7. django中的modelform和modelfoemset

    一. ModelForm ModelForm是根据Model来定制的Form 二. ModelForm的创建 from django import forms from app import mode ...

  8. metasploit渗透测试魔鬼训练营环境

    metasploitable winxpensp2 owasp_broken_web_apps win2k3 metasploitable 链接:https://pan.baidu.com/s/1oZ ...

  9. 防范 SQL 注入攻击

     防范 SQL 注入攻击 我们执行的 SQL语句中包含变量,执行的时候会直接把变量内容替换进去.而如果攻击者在输入框中输入一些危险的字符(通常包含 SQL 注释符 --,以及其他预先精心设置的内容), ...

  10. 默认空间和webapps下项目部署

    用eclipse默认的工作区和webapps的细节 用{WORKSPACE}表示你的eclipse的工作空间根目录,然后你打开 {WORKSPACE}\.metadata\.plugins\org.e ...