关于javascript:理解原型原型链

5次阅读

共计 4435 个字符,预计需要花费 12 分钟才能阅读完成。

对于搞前端的小伙伴来说,不论是老手还是老鸟,我想对于原型应该都被折腾过,总是云里雾里的感觉,要是原型都没搞明确,你还好意思说你是前端攻城狮?

对于对象

当一说到面向对象 (Object-Oriented OO) 时,你第一反馈必定想到类、对象、接口实现等概念,那咱们这里为啥已上来就说对象呢?因为 ECMAScript 里没有类,另外因为 ECMAScript 中的函数没有签名,所以也没有接口

ECMAScript-262 中对象定义为:“无序属性的汇合,其属性能够是根本值、对象或者函数”。因而从数据结构的角度,能够把对象看成散列表(Hash Table)。

对象分类

从对象的创立形式上能够把对象分成:内置对象、宿主对象、自定义对象三大类。对于对象分类具体点这里。

特地须要强调的是,除了 number、string、boolean、null、undefined、symbol 这 6 中根本类型外,其它通通都是对象 (援用类型),包含函数, 所有的函数都是对象,反之则不成立

对象和函数的关系

对象的创立

后面说过,ECMAScript 中没有类,那怎么创建对象呢?

对象字面量
// 形式一:对象字面量
var zhangsan = {
    type: "人类",
    name: "张三",
    age: 18,
    greeting: function() {console.log(`hello I'am ${this.name}`);
    }
};
zhangsan.greeting(); // "hello I'am 张三 "

该形式次要有一下几个问题:

  • 当要创立多个变量的时候,不得不写大量反复代码;
  • 每个实例都会持有一个 greeting 函数,但实际上性能都一样,没有复用,浪费资源;
  • 创立所有“人类 ”(type=” 人类 ”)的实例,type 的值都是一样的,然而每个实例还是持有一个独立的正本;
  • 创立实例无奈辨认类型(也就是说创立的实例具体是啥类型不晓得,只晓得它是 Object 的实例)。
工厂模式
// 形式二:工厂模式
function createPerson (name, age) {var p = new Object();
    p.type = "人类";
    p.name = name;
    p.age = age;
    p.greeting = greeting;
    return p;
}
var lisi = createPerson ("李四", 20);
lisi.greeting(); // "hello I'am 李四 "
function greeting () {console.log(`hello I'am ${this.name}`);
}

形式二尽管进行了封装,防止了创立时大量反复的代码,也通过把 greeting 抽离到全局作用域而解决了多个实例持有多个 greeting 正本的问题,但同时也给全局空间引入了一个只有该类型实例才会援用的函数,净化了全局空间;最初它也米有解决对象辨认问题。

// 形式三:构造函数
function Person (name, age) {
    this.type = "人类";
    this.name = name;
    this.age = age;
    this.greeting = greeting;
}
var wangwu = new Person("王五", 24); // wangwu instanceof Person === true
wangwu.greeting(); // "hello I'am 王五 "
function greeting () {console.log(`hello I'am ${this.name}`);
}

这个形式近乎完满了,解决了对象辨认问题,然而任然没有解决共享函数净化全局空间的问题;为了解决这个问题,上面请出咱们的配角 prototype(原型)。

原型 & 原型链

终于切入正题了,要解决下面形式三面临的问题,就要有一个属于构造函数专有(不必定义到全局净化全局空间),可能为构造函数创立的所有对象实例所共享的对象。这个对象就是原型(或称为原型对象)。

什么是原型(prototype)

默认状况下,任何函数都有一个属性 prototype,它是一个指针,指向一个对象(原型对象),原型对象的用处是蕴含特定类型实例所共享的属性和办法,默认原型对象只有一个constructor 属性,咱们能够给它定义更多属性和办法。

// 形式四:原型法
function Person (name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.type = "人类";
Person.prototype.greeting = function () {console.log(`hello I'am ${this.name}`);
};
var wangwu = new Person("王五", 24); // wangwu instanceof Person === true
wangwu.greeting(); // "hello I'am 王五 "

那下面的实例 wangwu 是怎么找到原型对象里定义的 greeting 的呢?起因是所有的对象都有一个外部指针,指向实例构造函数的原型对象,ECMAScript-262 第 5 版中称为 [[Prototype]],尽管规范并没有定义怎么拜访这个外部指针,然而 Firefox、Safari、Chrome 在每个对象上都反对一个指向雷同、名为__proto__ 指针属性。

在 chrome console 里查看 wangwu 的属性如下图:
[站外图片上传中 …(image-1d07-1644313611733)]

原型链查找

当对象实例拜访某个属性或调用某个办法时,首先在自有属性里找,找到则返回值或发动调用,没有则沿着 __proto__ 的指向往上找,直到最初查到 Object.prototype, 任然没有查到,即终止并报错。

对象实例、构造函数、构造函数的原型对象这三者的关系如下图:

上图中红色的门路及为查找方向,这条有 __proto__ 指针串起来的链即为 原型链 (prototype chain) 原型链的实质是一串程序指向原型对象的指针列表

原型的动态性

因为对象实例的 __proto__ 仅仅是一个指向原型对象的指针,因而对原型对象的批改立刻能够在实例上体现进去,哪怕这个实例在批改原型之前创立的:

Person.prototype.work = function () {console.log('work function');
}
// 这里的 wangwu 是下面创立的实例, 给原型减少 work 办法后,能够立刻调用
wangwu.work(); // "work function"

然而如果重写整个原型对象后,相当于为构造函数指定了新的原型对象,而已创立的实例的 __proto__ 依然指向旧原型对象,因而拜访不到在新原型里定义的办法:

Person.prototype = {work: function () {console.log('work function');   
    }
};
// 报错
wangwu.work(); // "wangwu.work is not a function"
// 在批改原型对象后创立的实例,因为获取到的__proto__属性是指向新原型的,因而不会报错
var sanma = new Person('三毛', 30);
// 能够欢快的“工作”sanma.work(); // "work function"

[图片上传失败 …(image-b3adea-1644313611733)]

笼罩整个原型对象后,相当于下面图中原来的 prototype 指向被切断了,指向了新的原型。

小结一下

默认状况下(因为原型对象实际上是可写的,因而能够被扭转):

  1. 任何函数都有一个指向其原型对象的指针属性 prototype;
  2. 任何对象实例都有一个指向其构造函数原型对象的外部指针[[Prototype]](__proto__)
  3. 原型对象也是对象,因而也有__proto__(例如上图中指向 Object.prototype 那个);
  4. 对象实例的 __proto__ 指针指向构造函数的原型对象:wangwu.__proto__ === Person.prototype
  5. 原型对象的 constructor 属性指向构造函数:Person.prototype.constructor === Person
  6. 构造函数和对象实例没有间接分割,仅仅是都有一个指针属性指向同一个原型对象。

对象实例辨认(检测)

咱们晓得,对于 number、string、boolean、undefined、function 这几种类型值,能够通过 typeof 操作符简略辨别,然而对于除 function 外的援用类型实例和 null,typeof 都返回 ”object”, 然而再往细了辨别,某个对象实例是神类型的实例,typeof 就没方法了。

instanceof 操作符

要辨认具体的对象实例类型,就要用到 instanceof 操作符,格局为 instance instanceof Func, instance 是待检测实例对象,Func 是一个构造函数,有了下面原型链的了解,那 instanceof 的检测机制就简略多了,只有在 instance 的原型链上某个__proto__ 指向了 Func 的原型对象,就返回 true,否则返回 false。即:

instance.__proto__...__proto__ === Func.prototype

另外也能够用 Func.prototype.isPrototypeof(instance)、Object.getPrototypeof(instance) === Func.prototype 来判断。

console.log(wangwu instanceof Person); // true
console.log(wangwu instanceof Object); // true
console.log(Person.prototype.isPrototypeof(wangwu)); // true
console.log(Object.prototype.isPrototypeof(wangwu)); // true
console.log(Object.getPrototypeof(wangwu) === Person.prototype); // true
console.log(Object.getPrototypeof(wangwu) === Object.prototype); // false, 因为 getPrototypeof 函数只返回实例原型,而不会返回原型链上的其它原型

原型继承

了解了原型,那原型继承就很简略了,须要扩大的类指向父类的原型即可,上面是简略的原型继承实现:

function Men() {//}


Men.prototype = Object.create(Person.prototype);
Men.prototype.constructor = Men;

特地留神,给 prototype 属性赋值后,Men.prototype.constructor 指向了 Person,因而必须再把它指回 Men。

正文完
 0