构造函数

构造函数主要用于初始化新对象。按照惯例,构造函数名第一个字母都要大写。

构造函数有别于其它函数在于它使用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基础知识温习:构造函数与原型的更多相关文章

  1. js基础知识温习:Javascript中如何模拟私有方法

    本文涉及的主题虽然很基础,在很多人眼里属于小伎俩,但在JavaScript基础知识中属于一个综合性的话题.这里会涉及到对象属性的封装.原型.构造函数.闭包以及立即执行表达式等知识. 公有方法 公有方法 ...

  2. Js基础知识(二) - 原型链与继承精彩的讲解

    作用域.原型链.继承与闭包详解 注意:本章讲的是在es6之前的原型链与继承.es6引入了类的概念,只是在写法上有所不同,原理是一样的. 几个面试常问的几个问题,你是否知道 instanceof的原理 ...

  3. js基础知识温习:js中的对象

    在JavaScript中对象是一个无序属性的集合,其属性可以包含基本值.对象或者函数. 对象最简单的创建方式 JavaScript中创建对象最简单的方式就是创建一个Object对象的实例,然后再添加属 ...

  4. [JS复习] JS 基础知识

    项目结尾,空闲时间,又把<JS 基础知识> 这本书过了一遍,温故知新后,很多知其然不知其所以然的内容 豁然开朗. [1. 用于范围的标签] display  :inline or bloc ...

  5. HTML+CSS+JS基础知识

    HTML+CSS+JS基础知识 目录 对HTML+CSS+JS的理解 基础知识 对HTML+CSS+JS的理解 基础知识 插入样式表的三种方式 外部样式表:<link rel="sty ...

  6. Node.js基础知识

    Node.js入门   Node.js     Node.js是一套用来编写高性能网络服务器的JavaScript工具包,一系列的变化由此开始.比较独特的是,Node.js会假设在POSIX环境下运行 ...

  7. 网站开发进阶(十五)JS基础知识充电站

    JS基础知识充电站 1.javascript alert弹出对话框时确定和取消两个按钮返回值? 用的不是alert对话框,是confirm confirm(str); 参数str:你要说的话或问题: ...

  8. NodeJs>------->>第三章:Node.js基础知识

    第三章:Node.js基础知识 一:Node.js中的控制台 1:console.log.console.info  方法 console.log(" node app1.js 1> ...

  9. JS基础知识笔记

    2020-04-15 JS基础知识笔记 // new Boolean()传入的值与if判断一样 var test=new Boolean(); console.log(test); // false ...

随机推荐

  1. 知方可补不足~sqlserver中对xml类型字段的操作

    回到目录 在sqlserver中有很多种数据类型,而XML数据类型是比较新奇怪的一种格式,我们平常接触的可能比较少,用的也少,而在某些场合,使用XML类型可能会使我们的开发变简单,下面就是一种情况: ...

  2. 朝花夕拾之--大数据平台CDH集群离线搭建

    body { border: 1px solid #ddd; outline: 1300px solid #fff; margin: 16px auto; } body .markdown-body ...

  3. yum或apt基本源设置指南

    关于: 管理Linux服务器的运维或开发人员经常需要安装软件,最常用方式应该是通过Linux系统提供的包管理工具来在线安装,比如centos的yum,ubuntu或debian的apt-get.当然这 ...

  4. JavaScript日期控件,用select实现

    <!doctype html> <html> <head> <title>年月日</title> </head> <bod ...

  5. linux下motion摄像头监控编译与配置

    利用linxu下的开源的motion搭建嵌入式视频动态监控系统 所谓移动图像监测,简单来说就是利用摄像头定点监测某个区域,当有移动物体经过时,摄像头便自动抓拍(要监测多大物体.按拍照速率都是可调的), ...

  6. 学习OpenStack之(5):在Mac上部署Juno版本OpenStack 四节点环境

    0. 前沿 经过一段时间的折腾,终于在自己的Mac上装好了Juno版本的四节点环境.这过程中,花了大量的时间,碰到了许多问题,学到不少知识,折腾过不少其实不需要折腾的东西,本文试着来对这过程做个总结. ...

  7. [转]基于display:table的CSS布局

    当IE8发布时,它将支持很多新的CSS display属性值,包括与表格相关的属性值:table.table-row和table-cell,它也是最后一款支持这些属性值的主流浏览器.它标志着复杂CSS ...

  8. jquery实现输入框聚焦,键盘上下键选择城市

    在最近有个项目中 需要实现当文本框聚焦的时候,可以键盘上下键选择内容,按enter键的时候,把内容传到输入框中,如图所示: 实现代码如下: /** *输入框聚焦,键盘上下键选择城市 */ ;(func ...

  9. Java正则获取邮箱

    实现思路 1.使用Java.net.URL对象,绑定网络上某一个网页的地址 2.通过java.net.URL对象的openConnection()方法获得一个HttpConnection对象 3.通过 ...

  10. JavaScript RegExp 对象

    JavaScript RegExp 对象 RegExp 对象用于规定在文本中检索的内容. 什么是 RegExp? RegExp 是正则表达式的缩写. 当您检索某个文本时,可以使用一种模式来描述要检索的 ...