前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端基础-JavaScript原型

前端基础-JavaScript原型

作者头像
cwl_java
发布2020-03-26 16:09:08
2910
发布2020-03-26 16:09:08
举报
文章被收录于专栏:cwl_Javacwl_Java

第3章 原型

学习目标

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

3.1 构造函数的 prototype属性

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

原型也是一个对象,原型对象上的所有属性和方法,都能被子对象 (派生对象) 共享 通过构造函数生成实例对象时,会自动为实例对象分配原型对象。 而每一个构造函数都有一个prototype属性,这个属性就是实例对象的原型对象

null没有自己的原型对象。

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

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

代码语言:javascript
复制
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 构造函数、实例、原型三者之间的关系

在这里插入图片描述
在这里插入图片描述

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

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

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

代码语言:javascript
复制
console.log(cat1.__proto__ == Cat.prototype); // true

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

3.3 原型对象的获取及修改

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

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

2:通过实例对象的 _proto_ 属性获取: 实例对象.__proto__

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

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

代码语言:javascript
复制
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(实例对象) 方法返回一个对象的原型对象。

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

代码语言:javascript
复制
function Cat(name, color) {
    this.name = name;
}
var cat1 = new Cat('猫'); //获取cat1对象的原型对象
var s = Object.getPrototypeOf(cat1); 
console.log(s);

Object.setPrototypeOf(实例对象,原型对象) 为现有对象设置原型对象 第一个是实例对象,第二个是要设置成为实例对象的原型对象的对象 这是设置原型对象的标准方法。

代码语言:javascript
复制
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新标准中添加的;

重要图示

在这里插入图片描述
在这里插入图片描述

3.4 原型及原型链

所有对象都有原型对象;

代码语言:javascript
复制
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 。 为减少不必要的输入,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象:

代码语言:javascript
复制
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 的指向正确,建议的写法是:

代码语言:javascript
复制
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
  • ……

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

例:

代码语言:javascript
复制
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 原型对象的问题及使用建议

性能问题:

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

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

代码语言:javascript
复制
//声明构造函数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

代码语言:javascript
复制
//声明构造函数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
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第3章 原型
    • 3.1 构造函数的 prototype属性
      • 3.2 构造函数、实例、原型三者之间的关系
        • 3.3 原型对象的获取及修改
          • 3.4 原型及原型链
            • 3.5 更简单的原型语法
              • 3.6 原生对象的原型
                • 3.7 原型对象的问题及使用建议
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档