构造函数

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

构造函数有别于其它函数在于它使用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. js日期校验

    当查询条件含有日期时,如"2012-3-4",查询前校验输入的日期字符串是否为有效的日期 var snapshot_createTime_begin=$(selector+&quo ...

  2. 烂泥:yum的使用及配置

    本文由秀依林枫提供友情赞助,首发于烂泥行天下. 最近由于服务器需求,需要在公司内网搭建内网yum源. 搭建内网yum源需要分以下几个步骤,如下: 1. yum是什么 2. repo文件是什么 3. r ...

  3. js有关时间日期的操作

    var myDate = new Date();var date_string = myDate.getFullYear()+""+((myDate.getMonth()+1)&l ...

  4. 定时器的应用---查询方式---让8个LED灯,左右各4个来回亮

    定时器的应用,查询方式.让8个LED灯,左右各4个来回亮 代码: /********************** 查询方式是主程序不断的查询是否中断,而不需要准备子程序 *************** ...

  5. Bootstrap 学习(1)

    简介 Bootstrap,来自 Twitter,是目前最受欢迎的前端框架.Bootstrap 是基于 HTML.CSS.JAVASCRIPT 的,它简洁灵活,使得 Web 开发更加快捷. Bootst ...

  6. [转]KendoUI系列:Grid

    本文转自:http://www.cnblogs.com/libingql/p/3774879.html 1.基本使用 <div id="grid"></div&g ...

  7. 老话题:自己编写只截窗口客户区的截屏软件(VB2010)

    现在能实现截屏的软件很多,就不一一列举了,连WIN7都自带截屏软件,甚至OFFICE2010开始都有截屏的功能. 截屏软件虽多,无外乎三种截屏方式:全屏截图.窗口截图.自定义矩形截图. 其中,窗口截图 ...

  8. Hash MD5 CRC 知识

    本文旨在科普安全相关的知识,并附一个C#实现的文件管理工具. Hash 安全散列算法(英语:Secure Hash Algorithm,缩写为SHA)是一个密码散列函数家族,是FIPS所认证的五种安全 ...

  9. BeanShell Assertion in Jmeter

    以下为几个beanshell assertion的栗子: if (ResponseCode != null && ResponseCode.equals ("200" ...

  10. 移动端API开发

    手机APP接口   接口:客户端发送一个请求到服务器 接口:1.URL地址:http://域名/控制器/方法  或者 https://    2.test.php处理客户端发送数据    3.数据返回 ...