前言
文章主要基于<< Javascrpt 高级程序设计3 >>总结的!!!
PS: 2018/05/09 基本重写了全文,补充知识点,新增实例,优化排版
PS: 2018/05/11 新增检测方法,技巧用法
构造函数
new constructor(arguments):
创建一个用护定义的对象类型的实例或具有构造函数的内置对象类型之一
- new命令: 执行构造函数返回一个实例对象
- 构造函数(constructor): 一个指定对象实例的类型的函数
- 传惨(arguments): 一个用来被构造函数调用的惨数列表
注意点几个:
- 默认构造函数首字母大写区分函数类型
- 在不传递任何惨数的情况new constructor可以省略括号
- 构造函数与其他函数的唯一区别: 就在于调用它们的方式不同.(任何函数,只要通过 new 操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过 new 操作符来调用,那它跟普通函数也不会有什麽两样)
- 使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍
- new存在的意义在于它实现了javascript中的继承,而不仅仅是实例化了一个对象
这是基本用法,大家都懂的,然后我们往深层次里挖掘下底层原理怎麽运作的.
假设有个函数
function Person(name) {
this.name = name || 'mike',
this.getAge = function () {
console.log(10);
}
}
var man = new Person;
var women = new Person('tracy')
console.log(man.name, women.name) // mike tracy
当代码执行时会经过几个步骤:
以下是我基于JS高程3总结的:
- 一个新的空对象被创建
- 创建执行的时候,将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
- 执行构造函数中的代码(为这个新对象添加属性)等初始化工作
- 如果构造函数返回了一个“对象”,那麽这个对象会取代整个new出来的结果.如果构造函数没有返回对象,那麽new出来的结果为步骤1创建的对象,(ps:一般情况下构造函数不返回任何值,如果想覆盖这个返回值,可以选择返回一个普通对象.);
以下是原版:
- 创建一个新对象
- 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象.
下面详细举例
1、如果构造函数不返回任何值则按照其他语言一样返回实例化对象(即步骤1创建的对象).
function Person() { }
var man = new Person();
console.log(man) // Person {}
2、如果构造函数返回的是基本类型 (string, number, boolean, null,undefined) 也按照其他语言一样返回实例化对象(即步骤1创建的对象).如果你们还搞不懂基本类型跟引用对象的区别,可以参考我之前写的文章关於javascript基本类型和引用类型小知识
function Person() {
return '我是基本类型'
}
var man = new Person();
console.log(man)//Person {}
3、若返回值是引用类型,则实际返回值为这个引用类型.
function Person() {
return {
age: 18
}
}
var man = new Person();
console.log(man) //Object {age: 18}
初学者特别应该注意的是他们之间是不同的,所谓的构造函数是创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一
正常来说构造函数不需要有返回值的,可以认为构造函数和普通函数的最大差别就是:构造函数中没有return语句,普通函数可以有return语句;构造函数中会使用this关键字定义成员变量和成员方法,普通的函数不会使用this关键字定义成员变量和方法.
像第二种情况即使返回基本类型也会被忽略掉,只有选择返回一个普通对象才会取代整个new出来的结果
原型对象(prototype)
每个函数都有一个 prototype
属性, prototype 就是指向通过调用构造函数而创建的那个对象实例的原型对象,作用是可以让所有对象实例共享它所包含的属性和方法.
在JavaScript
中一切皆对象,每个对象都是继承自另一个对象,所以对象都有自己的原型对象(除了null以外).
所以除了在构建函数内部定义属性方法共享之外,我们还可以在构造函数的原型对象上添加共享的自定义属性方法.
function Person() { }
//原型链添加函数
Person.prototype.getAge = function () {
console.log(18)
}
//实例化
var man = new Person(),
women = new Person();
man.getAge() // 18
women.getAge() // 18
有一种情况是对象实例自身已经赋有同名属性方法会覆盖 prototype 上的属性方法.这个认知是不准确的,下面的原型链会讲到这部分.
function Person() { }
//原型链添加函数
Person.prototype.getAge = function () {
console.log(18)
}
//实例化
var man = new Person();
man.getAge = function () {
console.log(81)
}
man.getAge() // 81
还有一种情况是在构造函数的原型对象上添加共享的自定义属性方法并且实例化对象之后,再次修改原型对象上的方法.同样会影响到输出结果,依然下面的原型链会讲到这部分.
function Person() { }
//原型链添加函数
Person.prototype.getAge = function () {
console.log(18)
}
//实例化
var man = new Person();
//修改原型链自定义函数
Person.prototype.getAge = function () {
console.log(81)
}
man.getAge() // 81
原型链
下面引出JS高程三解析片段----------
理解原型对象(关键词 prototype, constructor, __proto__)
无论什麽时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype
属性,这个属性指向函数的原型对象.在默认情况下,所有原型对象都会自动获得一个
constructor
(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针.就拿前面的例子来说,Person.prototype.constructor 指向 Person .而通过这个构造函数,我们还可继续为原型对象添加其他属性和方法.创建了自定义的构造函数之后,其原型对象默认只会取得 constructor 属性;至于其他方法,则都是从 Object 继承而来的.
当调用构造函数创建一个新实例后,该实例的内部将包含一个
指针(内部属性)
,指向构造函数的原型对象.ECMA-262 第 5 版中管这个指针叫
[[Prototype]]
.虽然在脚本中没有标准的方式访问 [[Prototype]] ,但 Firefox、Safari 和 Chrome 在每个对象上都支持一个属性__proto__ ;而在其他实现中,这个属性对脚本则是完全不可见的.不过,要明确的真正重要的一点就是,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间.
这里引出两个关键知识点:
- 所有对象都有属性__proto__指向该对象的构造函数的原型对象,原型链就是靠它形成的
- 函数对象除了__proto__,还有属性prototype指向该方法的原型对象,它的作用是:构造函数实例化对象的时候,告诉构造函数新创建的对象的原型是谁;
间单说构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针.原型链可以让我们做到利用原型让一个引用类型继承另一个引用类型的属性和方法.
Javascript 一切皆对象(普通对象和函数对象).每个函数对象都有一个内部链接到另一个对象它的原型 prototype.该原型对象有自己的原型,等等,直到达到一个以null为原型的对象.根据定义,null没有原型,并且作为这个原型链 prototype chain中的最终链接.
例如:
function Person() {
this.name = 'mike'
}
var man = new Person;
console.log(man.__proto__ === Person.prototype); // true
//继续深入发掘它的原型
console.log(Person.prototype.__proto__ === Object.prototype); // true
//前面说的一切皆对象是这个意思
console.log(Function.prototype.__proto__); // Object {}
console.log(Array.prototype.__proto__); // Object {}
console.log(Number.prototype.__proto__); // Object {}
//继续深入发掘它的最终来源是什麽
console.log(Object.prototype); // Object {}
console.log(Object.prototype.__proto__); // null
上面一步一步挖到最初的原型,有个注意的点容易混淆的,再强调一遍prototype是函数对象继承的原型,__proto__是指向创建它的函数对象的原型对象prototype.只有真的弄清楚关系你才知道下面的区别
console.log(Function.prototype); // function () {}
console.log(Array.prototype); // [Symbol(Symbol.unscopables): Object]
console.log(Number.prototype); // Number {[[PrimitiveValue]]: 0}
console.log(Object.prototype); // Object {}
//下面函数对象都是通过new Function()创建,所以指向必定都是Function.prototype.
console.log(Function.__proto__);
console.log(Array.__proto__);
console.log(Number.__proto__);
console.log(Object.__proto__);
//细细品味这句
console.log(Function.__proto__ === Function.prototype); // true
访问一个对象的属性时,它先在该对象上搜寻,如果该对象没有就搜寻该对象的原型,如果还没有就继续往该对象的原型的原型搜索,依此层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾(即undefined)位置.这就是上面说的实例方法覆盖原型对象方法认知不准确的解释了
function Person() {
this.name = 'mike'
}
Person.prototype.age = 10;
Person.prototype.getAge = function() {
console.log(20)
};
var man = new Person;
console.log(man.age) // 10
man.getAge() //20
console.log(man.sex) // undefined
原型对象除了__proto__之外还有一个constructor属性,作用是返回对创建此对象的函数的引用.这个比较间单,直接上实例
function Person() {
this.name = 'mike'
}
var man = new Person;
console.log(man.__proto__.constructor === Person); // true
console.log(new Array().constructor); // [Function: Array]
console.log(new Number().constructor); // [Function: Number]
console.log(new Object().constructor); // [Function: Object]
console.log(new Function().constructor); // [Function: Function]
实例化对象man打印结果如下.
man.__proto__.constructor === Person
获取检测原型对象方法
上面获取原型对象的方法其实不安全,
- 依赖浏览器环境暴露出来的访问属性,
- 在实例对象变更原型对象指向的情况会失效
function Person() {
this.name = 'mike'
}
var man = new Person;
man.prototype = Object;
console.log(man.prototype); // [Function: Object]
所以我们一般通过Object.getPrototypeOf
方法去获取.
function Person() {
this.name = 'mike'
}
var man = new Person;
man.prototype = Object;
console.log(man.prototype); // [Function: Object]
console.log(Object.getPrototypeOf(man)); // Person {}
如果是想要检测某个对象是否构造函数的实例,可以使用instanceof
function Person() {
this.name = 'mike'
}
var man = new Person;
console.log(man instanceof Person); // true
技巧用法
还记得上面说的构造函数与其他函数的唯一区别,就在于调用它们的方式不同.任何函数,只要通过 new 操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过 new 操作符来调用,那它跟普通函数也不会有什麽两样;
用个小技巧可以让你没有使用new操作符也能实例化构造函数,为了区别改了点代码.
function Person(name) {
//如果是实例化对象直接赋值
if (this instanceof Person) {
this.name = name
} else {
//否则重新实例化
return new Person(name)
}
}
var man = new Person('mike'),
women = Person('kitty');
console.log(man.name, women.name); // mike kitty
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。