原型定义: 给其它对象提供共享属性的对象,简称为原型( prototype )。
带着问题看原型
- 是什么(定义)?
- 为什么存在(作用)?
- 应用?
- 优缺点(注意事项)?
- 使用方法?
一、原型
1. 原型是什么
首先给出定义:给其它对象提供共享属性的对象,简称为原型( prototype )。
所有对象,都有一个隐式引用,它被称之为这个对象的 prototype 原型。当访问对对象的某个属性时,会先在对象本身的属性上寻找,没找到就会去他的原型对象上寻找。
__proto__ & constructor 概念简介
- proto:隐式原型,js标准里对象的原型无法直接通过属性名访问,但是多数浏览实现了__proto__(注意前后都有两个下划线)属性用来访问对象的原型。
- constructor:构造器,创建一个函数时会为它增加一个 prototype 属性,指向原型对象,原型对象自动获得一个名为 constructor 的属性,指回与之关联的构造函数。
2. 原型的作用
原型在Javascript中是一个非常重要的概念,那么它有什么作用呢?
作用在定义中已经提到了:共享属性
如果在一个对象上找不到某个属性,就会去它的原型对象上找,以此类推直至找到,或者寻找到原型链的终点都没找到则不存在这个属性;这个特性让任意创建的对象都拥有通用的各种方法,例如:toString、hasOwnProperty、isPrototypeOf等
3. 原型的应用
共享属性的特性可以用来做很多巧妙的事情。
3.1 属性委托
普通对象上的默认方法都是来自于 Object.prototype 上的方法
const obj = {};
obj.toString();复制代码
3.2 面向对象(类)封装
Javscript本身没有类,es6中的类也不过是寄生组合式封装的语法糖。
function Person() {}
Person.prototype.name = 'tom';
Person.prototype.sayName = function() {console.log(this.name);
};const person1 = new Person();
person1.sayName(); // "Nicholas"复制代码
3.3 继承
子类通过原型实现对父类属性和方法的继承,本质上是在子类实例上找不到的属性方法在子类的原型(父类的实例或原型)上找到了。
Javscript中的继承不是复制,而是委托。委托行为意味着某些对象(子类)在找不到属性或者方法引用时会把这个请求委托给另一个对象(父类)。
function SuperType(){this.name = 'tom';
}
SuperType.prototype.sayName = function(){
alert(this.name);
};function SubType(){}// 原型继承了 SuperTypeSubType.prototype = new SuperType();const instance = new SubType();
instance.sayName()); // 'tom'复制代码
关于Javascript继承的详细介绍请看笔者的另一篇文章:JavaScript面向对象&继承
4. 优缺点
优点
- 节约空间:Object.prototype上的众多方法(toString、hasOwnProperty、isPrototypeOf等)都不需要重新复制,通过原型链就可以找到并使用。
- 灵活度高:自由度大
缺点
- 难理解:别的语言没有,原型的原理细究起来也较为复杂。
- 易被覆盖和修改:引用类型的原型属性会被所有实例共享,所以修改也会被共享。
5. 注意事项
属性、方法的覆盖和屏蔽会导致意外的问题出现。
覆盖
Object.prototype.hasOwnProperty = function () {
alert('Ooops, 方法被覆盖了');return false;
}const obj = {name: 'tom'};console.log(obj.hasOwnProperty('name')); // 原型上的方法被屏蔽复制代码
屏蔽
由于查找对象属性的顺序是先原对象后原型,所以当对象本身存在和原型上的同名属性时,就会发生屏蔽。
function A() {this.name = 'aaa';
}
A.prototype.name = 'a';const instance = new A();console.log(instance.name); // 'aaa'原型上的属性被屏蔽复制代码
function A() {this.name = 'a';this.sayName = '';
}
A.prototype.sayName = function () {console.log(this.name);
};const instance = new A();
instance.sayName(); // 'aaa'原型上的方法被屏蔽复制代码
二、原型链
什么是原型链
定义:如果要访问对象中并不存在的一个属性,就会查找对象内部 prototype 关联的对象。在查找属性时会对它进行层层寻找遍历,这个关联关系实际上定义了一条原型链。
原型链最终会指向Object.prototype,原型链的终点是Object.prototype.__proto__ 也就是 null。
原型链示意图
- 继承中的原型链示意图:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SYzIjNjZTO0QTMzIDMhRjYhRWMjFDZxIWNwQDZwEGMh9CX4AzLcFDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL0M3Lc9CX6MHc0RHaiojIsJye.png)
三、使用
原型属性判断&设置
1. getPrototypeOf
Object.getPrototypeOf(obj) 间接访问指定对象的 prototype 对象。
版本:es6+
2. setPrototypeOf
Object.setPrototypeOf(obj, obj1) 间接设置指定对象的 prototype 对象
3. hasOwnProperty
是否仅属于对象本身的属性
枚举方法
1. getOwnPropertyNames
返回所有实例本身的属性。
2. in
只要通过对象可以访问,in 操作符就返回 true,也就是说在对象或者原型链上存在,就会返回true
3. for...in
可以通过对象访问且可以被枚举的属性都会返回,包括实例属性和原型属性。遮蔽原型中不可枚举([[Enumerable]]特性被设置为 false)属性的实例属性也会在 for-in 循环中返回,因为默认情况下开发者定义的属性都是可枚举的。
4. Object.keys
5. Object.values
const obj = {a: 1, b: '2'};Object.values(obj); // [1, '2']复制代码
6. Object.entries
const obj = {a: 1, b: '2'};Object.entries(obj); // [['a', 1], ['b', '2']]复制代码
7. 多种枚举方式的区别
- 枚举顺序
- for-in 循环和 Object.keys() 的枚举顺序是不确定的,取决于 JavaScript 引擎,可能因浏览器而异。
- Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()和 Object.assign() 的枚举顺序是确定性的。先以升序枚举数值键,然后以插入顺序枚举字符串和符号键。在对象字面量中定义的键以它们逗号分隔的顺序插入。
- 枚举范围
- getOwnPropertyNames、Object.keys、Object.values、Object.entries范围仅是实例本身的属性/值
- in、for...in、Object.entries范围仅是实例本身的属性/值