0
点赞
收藏
分享

微信扫一扫

前端基础-JavaScript原型


第3章 原型

学习目标


  • 使用 prototype 原型对象解决构造函数的问题
  • 理解什么是原型(原型对象)
  • 构造函数、prototype 原型对象、实例对象 三者之间的关系
  • 实例对象读写原型对象
  • 属性成员搜索原则:原型链
  • 原型对象的简写形式
  • 原生对象的原型
  • 原型对象的问题及使用建议

3.1 构造函数的 prototype属性

JavaScript 的每个对象都继承另一个父级​对象​,父级对象称为 **原型 ** (prototype)对象。

原型也是一个对象,原型对象上的所有属性和方法,都能被子对象 (派生对象) 共享

通过构造函数生成实例对象时,会自动为实例对象分配原型对象。

而每一个​构造函数​都有一个​prototype​属性,这个属性就是实例对象的​原型对象​。


null没有自己的原型对象。


这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在构造函数的 ​​prototype​​ 属性上,

也就是实例对象的原型对象上。

function Cat(color) {
this.color = color;
}

Cat.prototype.name = "猫";
Cat.prototype.sayhello = function(){
console.log('hello'+this.name,this.color);
}
Cat.prototype.saycolor = function (){
console.log('hello'+this.color);
}

var cat1 = new Cat('白色');
var cat2 = new Cat('黑色');
cat1.sayhello();
cat2.saycolor();

这时所有实例的 ​​name​​ 属性和 ​​sayhello()​​ 、​​saycolor​​ 方法,

其实都是同一个内存地址,指向构造函数的 ​​prototype​​ 属性,因此就提高了运行效率节省了内存空间。

3.2 构造函数、实例、原型三者之间的关系

前端基础-JavaScript原型_js

构造函数的prototyp属性,就是由这个构造函数new出来的所有实例对象的 原型对象

前面已经讲过,每个对象都有一个 ​​constructor​​ 属性,该属性指向创建该实例的构造函数

对象._​proto​_ (两边都是两个下划线):获取对象的原型对象;

console.log(cat1.__proto__ == Cat.prototype); // true


注意:ES6标准规定,__proto__属性只有浏览器环境下才需要部署,其他环境可以不部署,因此不建议使用


3.3 原型对象的获取及修改

上节可以看到,想要获取一个实例对象的原型对象,有两种方式:

1:通过实例对象的构造函数的prototype属性获取: ​​实例对象.constructor.prototype​

2:通过实例对象的 _​proto​_ 属性获取: ​​实例对象.__proto__​

而这两种方式,我们都不建议使用:

​obj.constructor.prototype​​在手动改变原型对象时,可能会失效。

function P() {};
var p1 = new P();

function C() {};
// 修改构造函数的prototype属性的值为p1
C.prototype = p1; //也就是说,此后所有有C构造函数得到的对象的原型对象都是p1;

var c1 = new C();

console.log(c1.constructor.prototype === p1) // false

推荐设置获取实例对象的原型的方式​:

Object.getPrototypeOf(实例对象) 方法返回一个对象的原型对象。

这是获取原型对象的标准方法。

function Cat(name, color) {
this.name = name;
}
var cat1 = new Cat('猫'); //获取cat1对象的原型对象
var s = Object.getPrototypeOf(cat1);
console.log(s);

Object.setPrototypeOf(实例对象,原型对象) 为现有对象设置原型对象

第一个是实例对象,第二个是要设置成为实例对象的原型对象的对象

这是设置原型对象的标准方法。

function Cat(name) {
this.name = name;
}
var ob = {p:'波斯'};
var cat1 = new Cat('猫');
//设置cat1的原型对象为ob
Object.setPrototypeOf(cat1,ob);
console.log(cat1.p);//cat1的原型对象中有p属性
console.log(Object.getPrototypeOf(cat1));
console.log(cat1.__proto__);
//注意:如果对象的原型被改变,不会影响构造函数获取的原型的结果
console.log(Cat.prototype == cat1.__proto__); //false


以上的两种方法,都是在ES6新标准中添加的;


重要图示

前端基础-JavaScript原型_原型对象_02

3.4 原型及原型链

所有对象都有原型对象;

function Cat(name, color) {
this.name = name;
}

var cat1 = new Cat('猫');

console.log(cat1.__proto__.__proto__.__proto__);

而原型对象中的属性和方法,都可以被实例对象直接使用;

每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性


  • 搜索首先从对象实例本身开始
  • 如果在实例中找到了具有给定名字的属性,则返回该属性的值
  • 如果没有找到,则继续搜索原型对象,在原型对象中查找具有给定名字的属性
  • 如果在原型对象中找到了这个属性,则返回该属性的值
  • 如果还是找不到,就到原型的原型去找,依次类推。
  • 如果直到最顶层的Object.prototype还是找不到,则返回undefined。

而这正是多个对象实例共享原型所保存的属性和方法的基本原理。

对象的属性和方法,有可能是定义在自身内,也有可能是定义在它的原型对象上。

由于原型本身也是对象,又有自己的原型,所以形成了一条 ​原型链​(prototype chain)。

3.5 更简单的原型语法

我们注意到,前面例子中每添加一个属性和方法就要敲一遍 ​​构造函数.prototype​​ 。

为减少不必要的输入,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象:

function Person (name, age) {
this.name = name
this.age = age
}

Person.prototype = {
type: 'human',
sayHello: function () {
console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
}
}

在该示例中,我们将 ​​Person.prototype​​ 重置到了一个新的对象。

这样做的好处就是为 ​​Person.prototype​​ 添加成员简单了,但是也会带来一个问题,那就是原型对象丢失了 ​​constructor​​ 成员(构造函数)。

所以,我们为了保持 ​​constructor​​ 的指向正确,建议的写法是:

function Person (name, age) {
this.name = name
this.age = age
}

Person.prototype = {
// 将这个对象的构造函数指向Person
//constructor: Person, // => 手动将 constructor 指向正确的构造函数
type: 'human',
sayHello: function () {
console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
}
}

var p = new Person();

3.6 原生对象的原型

所有构造函数都有prototype属性;


  • Object.prototype
  • Function.prototype
  • Array.prototype
  • String.prototype
  • Number.prototype
  • Date.prototype
  • ……

为内置对象扩展原型方法:

例:

var ar = [1,5,23,15,5];
//获取数组中小于10的数
function f(){
var minarr = [];
this.forEach(function(v,k){
if(v<10){
minarr.push(v);
}
})
return minarr;
}

Object.getPrototypeOf(ar).min10 = f;
console.log(ar.min10());//[1, 5, 5]

// 其他数组对象也具有相应的方法
var a = [1,2,34,7];
console.log(a.min10()); //[1, 2, 7]


这种技术被称为猴子补丁,并且会破坏封装。尽管一些流行的框架(如 Prototype.js)在使用该技术,但仍然没有足够好的理由使用附加的非标准方法来混入内置原型。


3.7 原型对象的问题及使用建议

性能问题:

在原型链上查找属性时是比较消耗资源的,对性能有副作用,这在性能要求苛刻的情况下很重要。

另外,​试图访问不存在的属性时会遍历整个原型链​。

//声明构造函数Man
function Man(name){
this.name = name;
this.p = function(){
console.log(this.name+'跑');
}
}

var m = new Man('张三');

console.log(m.hasOwnProperty('name')); // true
console.log(m.hasOwnProperty('age')); //false

​hasOwnProperty​​ 是 JavaScript 中唯一处理属性并且不会遍历原型链的方法。

注意:检查属性是否​​undefined​​​还不够。该属性可能存在,但其值恰好设置为​​undefined​​。

//声明构造函数Man
function Man(name){
this.name = name;
this.n = undefined;
this.p = function(){
console.log(this.name+'跑');
}
}

var m = new Man('张三');

if(m.n == undefined){
console.log('没有n属性')
}

console.log(m.hasOwnProperty('name')); // true
console.log(m.hasOwnProperty('name')); // true
console.log(m.hasOwnProperty('n')); //true



举报

相关推荐

0 条评论