构造函数

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

构造函数有别于其它函数在于它使用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. Spring boot 基于Spring MVC的Web应用和REST服务开发

    Spring Boot利用JavaConfig配置模式以及"约定优于配置"理念,极大简化了基于Spring MVC的Web应用和REST服务开发. Servlet: package ...

  2. pycharm active code

    43B4A73YYJ-eyJsaWNlbnNlSWQiOiI0M0I0QTczWVlKIiwibGljZW5zZWVOYW1lIjoibGFuIHl1IiwiYXNzaWduZWVOYW1lIjoiI ...

  3. my_mosaic

    //功能:输入想要打马赛克的坐标点,宽,高以及每个边需要划分的块数进行打马赛克 //只能处理位图,根据不同色深定义depth即可 //还没写从文件头读取图片分辨率 #include<unistd ...

  4. Hadoop2.6 datanode配置在线更新

    datanode 的配置可以在线更新了,http://blog.cloudera.com/blog/2015/05/new-in-cdh-5-4-how-swapping-of-hdfs-datano ...

  5. 鸿雁电器oa系统中决策支持模块效果

    公司简介鸿雁电器是国内著名的建筑电器产品的生产.经营企业,同时也是国家863计划CIMS(计算机集成制造系统)应用工程示范企业.浙江省高新技术企业.浙江省专利示范企业和杭州市信息化试点企业.企业系统泛 ...

  6. jquery常用方法

    一.多个按钮绑定同一事件 $("#index_svip,#index_svip_renew").click(function() { seajs.use(['svipLayer'] ...

  7. [转]Membership三步曲之入门篇 - Membership基础示例

    本文转自:http://www.cnblogs.com/jesse2013/p/membership.html Membership三步曲之入门篇 - Membership基础示例   Members ...

  8. Gprinter热敏打印机光栅位图点阵数据解析工具

    最近参与的项目有一个需求,解析佳博热敏打印机的光栅位图点阵数据并保存为图片文件.数据是通过Bus Hound抓取的,如下图所示. 其中1b 40为初始化打印机的指令,对应的ASCII码为ESC @,1 ...

  9. 洛谷10月月赛Round.1| P3399 丝绸之路 [DP]

    题目背景 张骞于公元前138年曾历尽艰险出使过西域.加强了汉朝与西域各国的友好往来.从那以后,一队队骆驼商队在这漫长的商贸大道上行进,他们越过崇山峻岭,将中国的先进技术带向中亚.西亚和欧洲,将那里的香 ...

  10. 1.使用using释放资源

    using语句有二个关键字 >>作为关键字,using可以导入命名空间 >>作为C#语句,using可以释放对象占用的内存资源 语法: using(SqlConnection ...