理解JavaScript之构造函数、原型和原型链

构造函数

构造函数模式的目的就是为了创建一个自定义类,并且创建这个类的实例。构造函数模式中拥有了类和实例的概念,并且实例和实例之间是相互独立的,即实例识别。

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

所以构造函数 ParentParent.prototype 和 p 的关系如下图。

image-20190211200314401

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

总结

  1. Symbol 作为构造函数来说并不完整,因为不支持语法 new Symbol(),但其原型上拥有 constructor 属性,即 Symbol.prototype.constructor
  2. 引用类型 constructor 属性值是可以修改的,但是对于基本类型来说是只读的,当然 nullundefined 没有 constructor 属性。
  3. __proto__ 是每个实例上都有的属性,prototype 是构造函数的属性,这两个并不一样,但 p.__proto__Parent.prototype 指向同一个对象。
  4. __proto__ 属性在 ES6 时被标准化,但因为性能问题并不推荐使用,推荐使用 Object.getPrototypeOf()
  5. 每个对象拥有一个原型对象,通过 __proto__ 指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null,这就是原型链。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值