深入了解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 相关文章推荐
javascript 特性检测并非浏览器检测
Jan 15 Javascript
JavaScript获取onclick、onchange等事件值的代码
Jul 22 Javascript
jQuery实现类似标签风格的导航菜单效果代码
Aug 25 Javascript
实例代码讲解jquery easyui动态tab页
Nov 17 Javascript
jquery+json实现动态商品内容展示的方法
Jan 14 Javascript
还不懂递归?读完这篇文章保证你会懂
Jul 29 Javascript
使用Nuxt.js改造已有项目的方法
Aug 07 Javascript
Vue项目总结之webpack常规打包优化方案
Jun 06 Javascript
在JavaScript中如何访问暂未存在的嵌套对象
Jun 18 Javascript
原生javascript制作的拼图游戏实现方法详解
Feb 23 Javascript
vue学习笔记之slot插槽用法实例分析
Feb 29 Javascript
在Vue中获取自定义属性方法:data-id的实例
Sep 09 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
php下过滤html代码的函数 提高程序安全性
2010/03/02 PHP
PHP封装的数据库模型Model类完整示例【基于PDO】
2019/03/14 PHP
php 自定义函数实现将数据 以excel 表格形式导出示例
2019/11/13 PHP
不用AJAX和IFRAME,说说真正意义上的ASP+JS无刷新技术
2008/09/25 Javascript
锋利的jQuery 第三章章节总结的例子
2010/03/23 Javascript
Jquery ajax传递复杂参数给WebService的实现代码
2011/08/08 Javascript
jquery+css+ul模拟列表菜单具体实现思路
2013/04/15 Javascript
javascript屏蔽右键代码
2014/05/15 Javascript
利用a标签自动解析URL分析网址实例
2014/10/20 Javascript
JavaScript输出当前时间Unix时间戳的方法
2015/04/06 Javascript
JavaScript实现仿网易通行证表单验证
2015/05/25 Javascript
jQuery实现延迟跳转的方法
2015/06/05 Javascript
Extjs4.0 ComboBox如何实现三级联动
2016/05/11 Javascript
自带气泡提示的vue校验插件(vue-verify-pop)
2017/04/07 Javascript
vue awesome swiper异步加载数据出现的bug问题
2018/07/03 Javascript
nodejs实现用户登录路由功能
2019/05/22 NodeJs
关于JS解构的5种有趣用法
2019/09/05 Javascript
详解JSON.stringify()的5个秘密特性
2020/05/26 Javascript
JavaScript 实现拖拽效果组件功能(兼容移动端)
2020/11/11 Javascript
详解Django通用视图中的函数包装
2015/07/21 Python
python实现爬虫统计学校BBS男女比例之数据处理(三)
2015/12/31 Python
Python下的Softmax回归函数的实现方法(推荐)
2017/01/26 Python
Python爬虫实现获取动态gif格式搞笑图片的方法示例
2018/12/24 Python
Python 实现中值滤波、均值滤波的方法
2019/01/09 Python
深入了解Python iter() 方法的用法
2019/07/11 Python
Python一键查找iOS项目中未使用的图片、音频、视频资源
2019/08/12 Python
python在OpenCV里实现投影变换效果
2019/08/30 Python
python使用 cx_Oracle 模块进行查询操作示例
2019/11/28 Python
Python中zip()函数的解释和可视化(实例详解)
2020/02/16 Python
Python按照list dict key进行排序过程解析
2020/04/04 Python
解决python cv2.imread 读取中文路径的图片返回为None的问题
2020/06/02 Python
详解python中GPU版本的opencv常用方法介绍
2020/07/24 Python
学校安全生产承诺书
2014/05/23 职场文书
干部作风建设心得体会
2014/10/22 职场文书
2014年幼儿园教师工作总结
2014/11/08 职场文书
Python Flask实现进度条
2022/05/11 Python