深入了解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 相关文章推荐
js触发onchange事件的方法说明
Mar 08 Javascript
实现网页页面跳转的几种方法(meta标签、js实现、php实现)
May 20 Javascript
js读取cookie方法总结
Oct 31 Javascript
如何编写高质量JS代码
Dec 28 Javascript
js实现鼠标感应向下滑动隐藏菜单的方法
Feb 20 Javascript
基于JavaScript实现文字超出部分隐藏
Feb 29 Javascript
浅谈JS中的!=、== 、!==、===的用法和区别
Sep 24 Javascript
原生Javascript插件开发实践
Jan 09 Javascript
在Vant的基础上封装下拉日期控件的代码示例
Dec 05 Javascript
vue实现菜单切换功能
May 08 Javascript
使用preload预加载页面资源时注意事项
Feb 03 Javascript
如何使用JavaScript策略模式校验表单
Apr 29 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
apache+codeigniter 通过.htcaccess做动态二级域名解析
2012/07/01 PHP
php实现memcache缓存示例讲解
2013/12/04 PHP
PHP网页游戏学习之Xnova(ogame)源码解读(十一)
2014/06/25 PHP
PHP实现取得HTTP请求的原文
2014/08/18 PHP
php求数组全排列,元素所有组合的方法
2016/05/05 PHP
YII框架页面缓存操作示例
2019/04/29 PHP
用JavaScript 处理 URL 的两个函数代码
2007/08/13 Javascript
基于NodeJS的前后端分离的思考与实践(四)安全问题解决方案
2014/09/26 NodeJs
JavaScript中继承用法实例分析
2015/05/16 Javascript
Angular 路由route实例代码
2016/07/12 Javascript
浅谈JavaScript中面向对象的的深拷贝和浅拷贝
2016/08/01 Javascript
Vue数据驱动模拟实现4
2017/01/12 Javascript
JS实现仿百度文库评分功能
2017/01/12 Javascript
微信小程序实现移动端滑动分页效果(ajax)
2017/06/13 Javascript
微信小程序图片轮播组件gallery slider使用方法详解
2018/01/31 Javascript
在小程序中使用Echart图表的示例代码
2018/08/02 Javascript
Jquery和CSS实现选择框重置按钮功能
2018/11/08 jQuery
Angular事件之不同组件间传递数据的方法
2018/11/15 Javascript
vue项目页面嵌入代码块vue-prism-editor的实现
2020/10/30 Javascript
Python中的os.path路径模块中的操作方法总结
2016/07/07 Python
python奇偶行分开存储实现代码
2018/03/19 Python
python交易记录整合交易类详解
2019/07/03 Python
python 使用装饰器并记录log的示例代码
2019/07/12 Python
Django 创建新App及其常用命令的实现方法
2019/08/04 Python
numpy创建单位矩阵和对角矩阵的实例
2019/11/29 Python
TensorFlow tf.nn.softmax_cross_entropy_with_logits的用法
2020/04/19 Python
Python 随机按键模拟2小时
2020/12/30 Python
北美三大旅游网站之一:Travelocity加拿大
2016/08/20 全球购物
彪马美国官网:PUMA美国
2017/03/09 全球购物
css animation配合SVG制作能量流动效果
2021/03/24 HTML / CSS
保护母亲河倡议书
2014/04/14 职场文书
留守儿童工作方案
2014/06/02 职场文书
大连星海广场导游词
2015/02/10 职场文书
酒店保洁员岗位职责
2015/02/26 职场文书
国庆放假通知怎么写
2015/07/30 职场文书
使用canvas仿Echarts实现金字塔图的实例代码
2021/11/11 HTML / CSS