深入了解js原型模式


Posted in Javascript onMay 30, 2019

一、什么是原型模式

在js中,创建对象的方式有工厂模式和构造函数模式等; 而构造函数模式最大的问题在于:构造函数中的每个方法都需要在实例对象中重新创建一遍,不能复用,所以为了解决这一个问题,就需要使用原型模式来创建对象。
原型模式是把所有实例共享的方法和属性放在一个叫做prototype(原型)的属性中 ,在创建一个函数时都会有个prototype属性, 这个属性是一个指针,指向一个对象,是通过调用构造函数而创建的那个对象实例的原型对象。

// 构造函数
function Person() {};
// 原型属性prototype
Person.prototype.name = '张三';
Person.prototype.sayName = function() {
console.log(this.name);
};
let person1 = new Person();
person1.sayName(); //张三
let person2 = new Person();
person2.sayName(); // 张三
console.log(person1.sayName == person2.sayName); //true

1.理解原型对象

无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象,在默认的情况下,所有的原型对象都自动获得一个constructor(构造函数)属性,这是一个指针,指向prototype属性所在的函数。创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性;其他的方法则是从Object继承来的。

当调用构造函数创建一个新实例对象后,该实例的内部将包含一个指针[[Prototype]],指向构造函数的原型对象。这个连接存在于实例和构造函数的原型对象之间,而不是存在实例和构造函数之间。

每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了就返回该属性的值,没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性,如果在原型对象中找到了这个属性,就返回该属性的值。

虽然可以通过实例访问保存在原型中的值,但不能通过实例对象重写原型中的值,如果在实例中添加一个在原型中的同名属性,该属性会自动屏蔽原型中的属性,但是不会修改原型中的属性,只会阻止访问原型中的属性,通过delete操作符则可以完全删除实例属性,使得可以重新访问原型中的属性。

2.原型与in操作符

hasOwnProperty()方法可以检测一个属性是否存在于实例对象中,

// 构造函数
function Person() {
this.age = 16;
};
Person.prototype.name = "张三";
let person1 = new Person();
console.log(person1.hasOwnProperty('name')); // false
console.log(person1.hasOwnProperty('age')); // true

in操作符的使用可以分为两类,单独使用和在for-in循环使用,在单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。

// 构造函数
function Person() {}
Person.prototype.name = 'zhang';
let person1 = new Person();
console.log('name' in person1); // true
person1.age = 14;
console.log('age' in person1); // true

同时使用hasOwnProperty()方法和in操作符,可以确定该属性时在原型上还是在存在于对象中。

// 构造函数
function Person() {}
function hasPrototypeProperty(object, name) {
return !object.hasOwnProperty(name) && (name in object);
}
Person.prototype.name = "张三";
let person = new Person();
console.log(hasPrototypeProperty(person, 'name')); // true
console.log(hasPrototypeProperty(person, 'age')); // false

使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的属性,其中即包含存在于实例中的属性,也包含与存在原型中的属性。

let o = {
name: 'san',
age: 14,
};
for(let key in o) {
console.log(key);
}

要取得对象上所有可枚举的实例属性,可以使用Object.keys()方法,接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。

如果想得到所有实例属性。无论是否可枚举,都可以使用Object.getOwnPropertyNames()方法。

3.更简单的原型语法

为了减少不必要的输入和从视觉上更好的封装原型的功能,常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象。

// 构造函数
function Person() {};
Person.prototype = {
sayHi: function() {
console.log(hi);
},
name: '张三',
};

通过这个方式会导致原型对象中的constructor属性不在指向Person了。如果constructor的值真的很重要,可以像下面这样特意将它设置回适当的值。

// 构造函数
function Person() {};
Person.prototype = {
constructor: Person,
sayHi: function() {
console.log(hi);
},
name: '张三',
};

但是通过这种方式会导致对象的[[Enumerable]]特性被设置为ture,默认情况下,constructor属性时不可枚举的,可以通过Object.defineProperty()解决这个问题。

// 构造函数
function Person() {};
Person.prototype = {
sayHi: function() {
console.log(hi);
},
name: '张三',
};
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
}

4.原型的动态性

当对原型对象所做的任何修改都能够立即从实例上反应出来。

function Person() {};
var friend = new Person();
Person.prototype.sayHi = function() {
console.log('hi');
};
friend.sayHi(); // hi

但是如果是重写整个原型对象,那么情况就不一样了。调用构造函数时会为实例添加一个指向最初原型的[[prototype]]指针,而把原型修改为另外一个对象 就相当于切断了构造函数与最初原型之间的联系。 实例中的指针仅指向原型,而不是指向构造函数。

// 构造函数
function Person() {};
var friend = new Person();
Person.prototype = {
constructor: Person,
sayHi: function() {
console.log(hi);
}
};
friend.sayHi(); // Uncaught TypeError: friend.sayHi is not a function

创建了一个Person的实例,然后又重写了其原型对象。但是在使用sayHi()时发生了错误,这个时候实例所指向的原型对象是一个新的对象。重写原型对象切断了现有原型与之前已经存在的对象实例直接的联系。所以报错了。

5.原生对象的原型

原型模式的重要性不仅体现在创建自定义类型方面,就连所有原生的引用类型,都采用这种模式,所有的原生引用类型(Object、Array、String)等,都在其构造函数的原型上定义了方法。可以像修改自定义对象的原型一样修改原生对象的原型。

二、原型模式的缺点

对于包含引用类型值的属性来说,所有实例在默认的情况下都会取得相同的属性值。

// 构造函数
function Person() {};
// 原型属性prototype
Person.prototype = {
constructor: Person,
friends: ['张三', '李四'],
}
let person1 = new Person();
let person2 = new Person();
person1.friends.push('王五');
console.log(person1.friends); // ["张三", "李四", "王五"]
console.log(person2.friends); // ["张三", "李四", "王五"]

由于friends存在于Person的原型对象中,所以person1对friends的修改也会通过person2反应出来,但是实例对象一般都是要有属于自己的全部属性,正因为如此,很少有人单独使用原型模式来创建对象。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
ExtJS 学习专题(一) 如何应用ExtJS(附实例)
Mar 11 Javascript
js获取GridView中行数据的两种方法 分享
Jul 13 Javascript
JavaScript获得url所有参数键值表的方法
Mar 21 Javascript
javascript动态创建链接的方法
May 13 Javascript
jQuery中extend函数详解
Jul 13 Javascript
js实现页面a向页面b传参的方法
May 29 Javascript
ES6深入理解之“let”能替代”var“吗?
Jun 28 Javascript
详解JS构造函数中this和return
Sep 16 Javascript
Javascript刷新页面的实例
Sep 23 Javascript
Angularjs之如何在跨域请求中传输Cookie的方法
Jun 01 Javascript
详解vue-flickity的fullScreen功能实现
Apr 07 Javascript
Vue 如何使用props、emit实现自定义双向绑定的实现
Jun 05 Javascript
js逆向解密之网络爬虫
May 30 #Javascript
Vue.js中的组件系统
May 30 #Javascript
Vue+Django项目部署详解
May 30 #Javascript
了解重排与重绘
May 29 #Javascript
小程序如何构建骨架屏
May 29 #Javascript
新手简单了解vue
May 29 #Javascript
详细讲解如何创建, 发布自己的 Vue UI 组件库
May 29 #Javascript
You might like
基于PHP5魔术常量与魔术方法的详解
2013/06/13 PHP
php判断表是否存在的方法
2015/06/18 PHP
PHP封装CURL扩展类实例
2015/07/28 PHP
javascript中的一些注意事项 更新中
2010/12/06 Javascript
JS复制到剪贴板示例代码
2013/10/30 Javascript
Jquery设置attr的disabled属性控制某行显示或者隐藏
2014/09/25 Javascript
JQuery实现的图文自动轮播效果插件
2015/06/19 Javascript
学习JavaScript设计模式之享元模式
2016/01/18 Javascript
BootStrap Validator 版本差异问题导致的submitHandler失效问题的解决方法
2016/12/01 Javascript
jQuery基于事件控制实现点击显示内容下拉效果
2017/03/07 Javascript
Grunt针对静态文件的压缩,版本控制打包的实例讲解
2017/09/29 Javascript
node.js通过axios实现网络请求的方法
2018/03/05 Javascript
React中如何引入Angular组件详解
2018/08/09 Javascript
js刷新页面location.reload()用法详解
2019/12/09 Javascript
javascript实现前端分页功能
2020/11/26 Javascript
[52:27]2018DOTA2亚洲邀请赛 3.31 小组赛B组 paiN vs Secret
2018/04/01 DOTA
[42:24]完美世界DOTA2联赛PWL S2 LBZS vs FTD.C 第三场 11.27
2020/12/01 DOTA
python动态性强类型用法实例
2015/05/09 Python
Python实现XML文件解析的示例代码
2018/02/05 Python
pandas.DataFrame 根据条件新建列并赋值的方法
2018/04/08 Python
python+pyqt5实现图片批量缩放工具
2019/03/18 Python
python字典的常用方法总结
2019/07/31 Python
python 申请内存空间,用于创建多维数组的实例
2019/12/02 Python
Python任务自动化工具tox使用教程
2020/03/17 Python
利用CSS3实现炫酷的飞机起飞动画
2016/09/17 HTML / CSS
艺术家策划的室内设计:Curious Egg
2019/03/06 全球购物
荷兰最大的儿童服装店:The Kids Republic
2019/04/13 全球购物
MYPROTEIN澳大利亚官方网站:欧洲运动营养品牌
2019/06/26 全球购物
俄罗斯小米家用电器、电子产品和智能家居商店:Poood.ru
2020/04/03 全球购物
Java面试中常遇到的问题,也是需要注意的几点
2013/08/30 面试题
求职简历的自我评价怎样写好
2013/10/07 职场文书
竞选团支书演讲稿
2014/04/28 职场文书
班级标语大全
2014/06/21 职场文书
“三支一扶”支教教师思想汇报
2014/09/13 职场文书
学生夜不归宿检讨书
2014/09/23 职场文书
写一个Python脚本自动爬取Bilibili小视频
2021/04/24 Python