构造函数
构造函数模式的目的就是为了创建一个自定义类,并且创建这个类的实例。构造函数模式中拥有了类和实例的概念,并且实例和实例之间是相互独立的,即实例识别。
constructor返回创建实例对象时构造函数的引用。这个属性是对函数本身的引用,而不是一个包含函数名称的字符串。
function Parent(age){
this.age = age;
}
let p = new Parent(50);
p.constructor === Parent; // true
p.constructor === Object; // false
构造函数本身就是一个函数,与普通函数没有任何区别,不过为了规范一般将其首字母大写。
区别:使用new生成实例的函数就是构造函数,直接调用的就是普通函数。
要是普通函数拥有constructor属性,可以这样:
function parent(age){
this.age = age;
}
let p = parent(50);
console.log(p.constructor) // undefined
function parent2(age){
return {
age = age;
}
}
let p2 = parent2(50);
console.log(p2.constructor === Object) // true
Symbol
Symbol是ES6中引入的一种新的基础数据类型,属于基本函数类型,功能类似于一种标识唯一性的ID。作为构造函数来说它并不完整,因为它并不支持new Symbol(),如果生成实例直接使用Symbol()即可。
new Symbol(123); // Symbol is not a constructor
Symbol(123); // Symbol(123)
PS:每个Symbol实例都是唯一的。因此,当你比较两个Symbol实例的时候,将总会返回false
:
let s1 = Symbol()
let s2 = Symbol('another symbol')
let s3 = Symbol('another symbol')
s1 === s2 // false
s2 === s3 // false
虽然是基本数据类型,但 Symbol(123)
实例可以获取 constructor
属性值。
let sym = Symbol(123);
console.log(sym); // Symbol(123)
console.log(sym.constructor); // ƒ Symbol() { [native code] }
这里的 constructor
属性来自 Symbol
原型,即 Symbol.prototype.constructor
返回创建实例原型的函数, 默认为 Symbol
函数。
PS:对于引用类型,constructor属性值可读,对于基本类型,constructor属性值只读(null,undefined没有construc属性)。
模拟实现new
特点:
1. 访问到构造函数里的属性
2. 访问到原型里的属性
3. 返回一个对象
4. 没有 return
,即返回 undefined
5. 返回undefined
以外的基本类型
function create() {
// 创建一个空的对象
let obj = new Object(),
// 获得构造函数,同时删除 arguments 中第一个参数
Con = [].shift.call(arguments);
// 链接到原型,obj 可以访问构造函数原型中的属性
Object.setPrototypeOf(obj, Con.prototype);
// 绑定 this 实现继承,obj 可以访问到构造函数中的属性
let ret = Con.apply(obj, arguments);
// 优先返回构造函数返回的对象
return ret instanceof Object ? ret : obj;
};
原型
JavaScript
是一种基于原型的语言 (prototype-based language),给其它对象提供共享属性的对象。
也就是说,prototype 自己也是对象,只是被用以承担某个职能罢了。
每个对象拥有一个原型对象,对象以其原型为模板,从原型继承方法和属性,这些属性和方法定义在对象的构造器函数的 prototype
属性上,而非对象实例本身。
在JavaScript中,每当定义一个函数数据类型(普通函数、类)时候,都会自带一个prototype
属性,这个属性指向函数的原型对象,并且这个属性是一个对象数据类型的值。
Parent
对象有一个原型对象 Parent.prototype
,其上有两个属性,分别是 constructor
和 __proto__
,其中 __proto__
已被弃用。
构造函数Parent有一个指向原型的指针,原型Parent.prototype
有一个指向构造函数的指针 Parent.prototype.constructor
,如上图所示,其实就是一个循环引用。
__proto__
上图可以看到 Parent 原型( Parent.prototype
)上有 __proto__
属性,这是一个访问器属性(即 getter 函数和 setter 函数),通过它可以访问到对象的内部 [[Prototype]]
(一个对象或 null
)。
这里用 p.__proto__
获取对象的原型,__proto__
是每个实例上都有的属性,prototype
是构造函数的属性,这两个并不一样,但 p.__proto__
和 Parent.prototype
指向同一个对象。
function Parent() {}
let p = new Parent();
p.__proto__ === Parent.prototype // true
所以构造函数 Parent
、Parent.prototype
和 p
的关系如下图。
PS:__proto__
属性在 ES6
时才被标准化,以确保 Web 浏览器的兼容性,但是不推荐使用,除了标准化的原因之外还有性能问题。为了更好的支持,推荐使用 Object.getPrototypeOf()
。
优化new
不建议使用__protot__,改用Object.creat()来模拟实现。
function create() {
// 1、获得构造函数,同时删除 arguments 中第一个参数
Con = [].shift.call(arguments);
// 2、创建一个空的对象并链接到原型,obj 可以访问构造函数原型中的属性
let obj = Object.create(Con.prototype);
// 3、绑定 this 实现继承,obj 可以访问到构造函数中的属性
let ret = Con.apply(obj, arguments);
// 4、优先返回构造函数返回的对象
return ret instanceof Object ? ret : obj;
};
原型链
在JavaScript中万物都是对象,对象和对象之间也有关系,并不是孤立存在的。对象之间的继承关系,在JavaScript中是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,称之为原型链。
每个对象拥有一个原型对象,通过 __proto__
指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null
。这种关系被称为原型链 (prototype chain),通过原型链一个对象会拥有定义在其他对象中的属性和方法。
当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。如果没有则去原型的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined。
function Parent(age) {
this.age = age;
}
let p = new Parent(50);
p.constructor === Parent; // true
这里 p.constructor
指向 Parent
,实例对象 p
本身没有 constructor
属性,是通过原型链向上查找 __proto__
,最终查找到 constructor
属性,该属性指向 Parent
。
function Parent(age) {
this.age = age;
}
let p = new Parent(50);
p; // Parent {age: 50}
p.__proto__ === Parent.prototype; // true
p.__proto__.__proto__ === Object.prototype; // true
p.__proto__.__proto__.__proto__ === null; // true
总结
Symbol
作为构造函数来说并不完整,因为不支持语法new Symbol()
,但其原型上拥有constructor
属性,即Symbol.prototype.constructor
。- 引用类型
constructor
属性值是可以修改的,但是对于基本类型来说是只读的,当然null
和undefined
没有constructor
属性。 __proto__
是每个实例上都有的属性,prototype
是构造函数的属性,这两个并不一样,但p.__proto__
和Parent.prototype
指向同一个对象。__proto__
属性在ES6
时被标准化,但因为性能问题并不推荐使用,推荐使用Object.getPrototypeOf()
。- 每个对象拥有一个原型对象,通过
__proto__
指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向null
,这就是原型链。