js基础知识温习:构造函数与原型
构造函数
构造函数主要用于初始化新对象。按照惯例,构造函数名第一个字母都要大写。
构造函数有别于其它函数在于它使用new操作符来调用生成一个实例对象。换句话说,如果一个函数使用new操作符来调用,则将其称为构造函数。
function User(name, age) {
this.name = name;
this.age = age;
}
// 调用
var jenemy = new User('jenemy', 25);
jenemy.name; // jenemy
与函数调用和方法调用的不同点在于,构造函数调用是将一个全新的对象作为this变量的值,并隐式返回这个新对象作为调用的结果。
typeof User; // function
typeof jenemy; // object
在JavaScript中每一个对象都有一个constructor属性指向创建这个对象的函数,函数同样也是一个对象。因此有
Person.constructor === Function; // true
jenemy.constructor === User; // true
如果调用构造函数时忘记使用new操作符,那么构造函数将作为一个普通函数调用,此时this将会被绑定到全局对象中。
var xiaolu = User('xiaolu', 25);
xiaolu.name; // Uncaught TypeError: Cannot read property 'name' of undefined
window.name; // xiaolu
window.age; // 25
更加健壮的方式是无论如何调用都按构造函数来工作。
function User(name, age) {
if (!(this instanceof User)) {
return new User(name, age);
}
this.name = name;
this.age = age;
}
var jenemy = new User('jenemy', 25);
var xiaolu = User('xiaolu', 25);
jenemy instanceof User; // true
xiaolu instanceof User; // true
这种模式虽然能够解决问题,但带来了另外一个问题:执行了二次User()函数的调用,因此代价有点高。此外,如果参数是可变的,这种方式也很难适用。
这里可以使用ES5的Object.create()来解决上述问题。
function User(name, age) {
var self = this instanceof User ? this : Object.create(User.prototype);
self.name = name;
self.age = age;
return self;
}
Object.create()方法是创建一个拥有指定原型和若干个指定属性的对象。这里需要注意的是它的第二个参数和Object.defineProperties()的第二个参数是一样的。
由于Object.create()只有在ES5才是有效的,因此需要对Object.create()进行Polyfill
if (typeof Object.create != 'function') {
// 使用匿名函数封装所有私有变量
Object.create = (function() {
function Temp() {};
// 更加安全的引用Object.prototype.hasOwnProperty
var hasOwn = Object.prototype.hasOwnProperty;
return function(O) {
// 如果 O 不是 Object 或者 null,抛出一个 TypeError 异常
if (typeof O != 'object') {
throw TypeError('Object prototype may only be an Object or null');
}
Temp.prototype = O;
var obj = new Temp();
Temp.prototype = null; // 释放临时对象资源
// 如果存在参数 Properties,而不是undefined,那么就把自身属性添加到 obj 上
if (arguments.length > 1) {
var Properties = Object(arguments[1]);
for (var prop in Properties) {
if (hasOwn.call(Properties, prop)) {
obj[prop] = Properties[prop];
}
}
}
return obj;
};
}) ();
}
上述polyfill实现了Object.create()的所有功能,其实这里只需要每一个参数就可以了,因此可以简化一下
if (Object.create != 'function') {
Object.create = function(O) {
function Temp() {};
if (typeof O != 'object') {
throw TypeError('Object prototype may only be an Object or null');
}
Temp.prototype = O;
return new Temp();
};
}
原型(prototype)
在JavaScript中prototype属性保存了引用类型所有实例方法的真正所在。拿数组操作来讲,push()方法实际上是保存在prototype名下,只不过是通过其对象的实例访问罢了。
// 实例化一个数组对象
var person = new Array();
// 调用实例化方法push
person.push('jenemy');
// 同样也可以直接调用Array.prototype.push方法。
// 同时注意将this指向当前person数组对象
Array.prototype.push.call(person, 'xiaolu');
person.length; // 2
无论什么时候,只要我们创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。
在上面介绍构造函数的时候提到过每个对象都有一个constructor属性指向创建它的函数。因此所有原型对象都会默认获得一个constructor属性指向prototype属性所在函数的指针。然后,当调用构造函数实例化一个新对象后,该实例内部会有一个标准的指针 [[Prototype]] 指向构造函数的实例对象。虽然没有一个标准的方式去访问 [[Prototype]],但 firefox、Safari 和 Chrome在每个对象上都支持一个属性__proto__。
注意一点的是__proto__实际上只存在于构造函数实例与构造函数原型之间,而不是存在于实例与构造函数之间。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.getName = function() {
return this.name;
}
var jenemy = new Person('jenemy', 25);
var xiaolu = new Person('xiaolu', 25);
jenemy.name; // jenemy
xiaolu.getName(); // xiaolu
为了便于理解各对象之间的关系,我们将其图形化展示:
|------------------------1---------------------------|
v |
|-----------------| |---------------------| |
| Person | ----->| Person.prototype | |
|-----------------| | |---------------------| |
| prototype |·|------------- | constructor |·|--
|-----------------| | |---------------------|
| name | String | | | getName | Function |
|-----------------| | |---------------------|
| age | String | 2
|-----------------| |
|---------------------------|
| |
|-----------------| | |-------------------| |
| jenemy | | | xiaolu | |
|-----------------| | |-------------------| |
| [[Prototype]] |·|------------| | [[prototype]] |·|-|
|-----------------| |-------------------|
| name | 'jenemy' | | name | 'xiaolu' |
|-----------------| |-------------------|
| age | 25 | | age | 25 |
|-----------------| |-------------------|
然后我们再用代码来验证一下图形所展示的对象之间的关系
// 验证线路1
Person.prototype.constructor === Person; // true
// 验证线路2
Person.prototype.isPrototypeOf(jenemy); // true
Object.getPrototypeOf(xiaolu) === Person.prototype; // true
jenemy.__proto__ === Person.prototype; // true
// 由于jenemy是由new Person()后得到的实例化对象,因此有
jenemy.constructor === Person; // true
这里的Object.isPrototypeOf()方法用于检查传入的对象是否是传入对象的原型。而Object.getPrototypeOf()方法返回指定对象的原型(也就是该对象的内部属性[[Prototype]]的值)。
上面我们有使用__proto__来获取对象的原型,但并不是所有的JavaScript环境都支持通过它来获取对象的原型,因此官方给出了一个标准解决方案就是使用Object.getPrototypeOf()方法。另外需要注意的是,拥有null原型的对象没有这个特殊的__proto__属性。
function Person(name, age) {
this.name = name;
this.age = age;
}
var jenemy = new Person('jenemy', 25);
var empty = Object.create(null);
'__proto__' in jenemy; // true
'__proto__' in empty; // false
Object.getPrototypeOf(empty); // null
由于Object.getPrototypeOf()方法是ES5中提供的方法,对于那些没有提供ES5 API的环境,也可以利用__proto__属性来实现Object.getPrototypeOf()函数。
if (typeof Object.getPrototypeOf === 'undefined') {
Object.getPrototypeOf = function(obj) {
var t = typoef obj;
if (!obj || (t!== 'object' && t!== 'function')) {
throw new TypeError('not an object');
}
return obj.__proto__;
}
}
在创建原型属性时,我们同样可以使用对象字面量语法:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
getName: function() {
return this.name;
}
}
Person.prototype.constructor === Person; // false
Person.proottype.constructor; // Object
然后,我们发现这里Person.prototype.constructor并没有指向创建它的构造函数,而是指向了Object,原因在于我们重写了Person.prototype对象,导致Person.prototype指向了Object,面Object.prototype.constructor本来就指向'Object'。解决办法是手机将Person.prototype.constructor指向Person。
Person.prototype = {
constructor: Person,
getName: function() {
return this.name;
}
}
Person.prototype.constructor === Person; // true
获取对象的属性列表
对于一个对象的属性遍历,最先想到的就是使用in操作符在for-in循环中使用。通过in操作符可以访问实例中和原型中的可枚举的属性。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.prop1 = 1;
var jenemy = new Person('jenemy', 25);
jenemy.addr = 'shanghai';
'name' in jenemy; // true
'prop1' in jenemy; // true
'addr' in jenemy; // true
另一个就是使用ES5提供的Object.keys()方法,它会返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,数组中属性名的排列顺序和使用for-in循环遍历该对象时返回的顺序一致(两者的主要区别是for-in会枚举原型链中的属性)。
Object.keys(jenemy); // ["name", "age", "addr"]
最后一个是使用ES5提供的Object.getOwnPropertyNames()方法,它会返回对象所有的实例属性,包括可枚举的和不可枚举的属性。
Object.getOwnPropertyNames(jenemy); // ["0", "1", "2", "length"]
这里length属性是Object对象的一个不可枚举的属性,因此会被输出。
区分实例属性和原型属性
在JavaScript中我们可以任意修改对象的属性和方法,甚至可以修改内置原型方法。当然,我们不是不建议修改内置对象方法和属性,这样会导致依赖该方法或者属性的其它调用者发生无法预期的结果。
当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。也就是说,它会阻止我们访问原型中的那个属性,但不会修改那个属性。当然,使用delete操作符可以完全删除实例属性,从而重新访问原型中的属性。
使用hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。只有属性存在于实例中时才会返回true。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.getName = function() {
return this.name;
}
var jenemy = new Person('jenemy', 25);
jenemy.getName(); // jenemy
jenemy.hasOwnProperty('getName'); // false
// 自定义实例属性
jenemy.getName = function() {
return '我的名字叫' + this.name + '我会屏蔽掉原型中的属性值。';
}
jenemy.getName(); // 我的名字叫jenemy我会屏蔽掉原型中的属性值。
jenemy.hasOwnProperty('getName'); // true
delete jenemy.getName;
jenemy.getName(); // jenemy
jenemy.hasOwnProperty('getName'); // false
参考
-《JavaScript高级程序设计》(第3版)
-《JavaScript面向对象精要》
-《Effective JavaScript》
js基础知识温习:构造函数与原型的更多相关文章
- js基础知识温习:Javascript中如何模拟私有方法
本文涉及的主题虽然很基础,在很多人眼里属于小伎俩,但在JavaScript基础知识中属于一个综合性的话题.这里会涉及到对象属性的封装.原型.构造函数.闭包以及立即执行表达式等知识. 公有方法 公有方法 ...
- Js基础知识(二) - 原型链与继承精彩的讲解
作用域.原型链.继承与闭包详解 注意:本章讲的是在es6之前的原型链与继承.es6引入了类的概念,只是在写法上有所不同,原理是一样的. 几个面试常问的几个问题,你是否知道 instanceof的原理 ...
- js基础知识温习:js中的对象
在JavaScript中对象是一个无序属性的集合,其属性可以包含基本值.对象或者函数. 对象最简单的创建方式 JavaScript中创建对象最简单的方式就是创建一个Object对象的实例,然后再添加属 ...
- [JS复习] JS 基础知识
项目结尾,空闲时间,又把<JS 基础知识> 这本书过了一遍,温故知新后,很多知其然不知其所以然的内容 豁然开朗. [1. 用于范围的标签] display :inline or bloc ...
- HTML+CSS+JS基础知识
HTML+CSS+JS基础知识 目录 对HTML+CSS+JS的理解 基础知识 对HTML+CSS+JS的理解 基础知识 插入样式表的三种方式 外部样式表:<link rel="sty ...
- Node.js基础知识
Node.js入门 Node.js Node.js是一套用来编写高性能网络服务器的JavaScript工具包,一系列的变化由此开始.比较独特的是,Node.js会假设在POSIX环境下运行 ...
- 网站开发进阶(十五)JS基础知识充电站
JS基础知识充电站 1.javascript alert弹出对话框时确定和取消两个按钮返回值? 用的不是alert对话框,是confirm confirm(str); 参数str:你要说的话或问题: ...
- NodeJs>------->>第三章:Node.js基础知识
第三章:Node.js基础知识 一:Node.js中的控制台 1:console.log.console.info 方法 console.log(" node app1.js 1> ...
- JS基础知识笔记
2020-04-15 JS基础知识笔记 // new Boolean()传入的值与if判断一样 var test=new Boolean(); console.log(test); // false ...
随机推荐
- R语言中的循环函数(Grouping Function)
R语言中有几个常用的函数,可以按组对数据进行处理,apply, lapply, sapply, tapply, mapply,等.这几个函数功能有些类似,下面介绍下这几个函数的用法. Apply 这是 ...
- c#-轮询算法
这两天做东西,业务上有个特殊的需求,在用户访问页面的时候,针对某一行代码进行控制,按照概率来进行显示,我做的是针对当前页面的曝光进行处理,曝光代码是第三方的,页面上只要有这段代码就算是执行了这段曝光代 ...
- W3School-CSS 文本实例
CSS 文本实例 CSS 实例 CSS 背景实例 CSS 文本实例 CSS 字体(font)实例 CSS 边框(border)实例 CSS 外边距 (margin) 实例 CSS 内边距 (paddi ...
- shell脚本的执行
shell脚本有两种执行方式,一种是直接执行,一种是使用$source 或.命令执行 直接执行 直接执行shell脚本,bash会在当前bash下新建一个子bash进程用来执行shell脚本,此时脚本 ...
- linux清理内存命令
1.清理前内存使用情况 free -m 2.开始清理 echo 1 > /proc/sys/vm/drop_caches3.清理后内存使用情况 free -m4.完成! 查看内存条数命令: # ...
- linux GD库安装
GD 安裝第一部需要做的是先要安裝 GD 到系統內,而安裝 GD 前需要安裝 jpeg-6b, libpng, zlib, freetype.以下是下载网址:GD 2.0.33jpeg-6blibpn ...
- AStar算法的学习
摘自:http://www.cnblogs.com/hxsyl/p/3994730.html A*算法的java实现 import java.util.ArrayList; import java.u ...
- Neutron VxLAN + Linux Bridge 环境中的网络 MTU
1. 基础知识 1.1 MTU 一个网络接口的 MTU 是它一次所能传输的最大数据块的大小.任何超过MTU的数据块都会在传输前分成小的传输单元.MTU 有两个测量层次:网络层和链路层.比如,网络层 ...
- C++浅析——继承类内存分布和虚析构函数
继承类研究 1. Code 1.1 Cbase, CTEST为基类,CTest2为其继承类,并重新申明了基类中的同名变量 class CBase { public: int Data; CBase() ...
- Putty文件夹蓝色太暗问题
Putty文件夹蓝色太暗问题 用Putty通过ssh登陆Linux服务器时,有时候会发现系统默认的蓝色字体太暗,具体解决方法如下: (1)打开putty客户端,选择某登陆Session,然后load: ...