Typescript之原型对象prototype深入了解

Typescript之原型对象prototype



前言

本文主要描述在Typescript下的原型对象prototype以及获取原型对象的方法。


提示:以下是本篇文章正文内容,下面案例可供参考

一、prototype是什么?

在JavaScript中,prototype对象是实现面向对象的一个重要机制。每个函数就是一个对象(Function),函数对象都有一个子对象 prototype对象,类是以函数的形式来定义的。prototype表示该函数的原型,也表示一个类的成员的集合。在JavaScript中,当谈到继承时,JavaScript 只有一种结构:对象。在 ES2015/ES6 中引入了 class 关键字,但那只是语法糖,JavaScript 仍然是基于原型的。

1.对象实例的__proto__属性

每个实例对象( object )都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

2.Object.getPrototypeOf()获取对象实例的__proto__属性

遵循ECMAScript标准,someObject. __proto__ 符号是用于指向 someObject 的原型对象prototype。从 ECMAScript 6 开始 __proto__ 可以通过 Object.getPrototypeOf() 和Object.setPrototypeOf() 访问器来访问。这个等同于 JavaScript 的非标准但许多浏览器实现的属性 __proto__。但它不应该与构造函数 Function 的 prototype 属性相混淆。被构造函数创建的实例对象的 __proto__ 指向 Function 的 prototype 属性。Object.prototype 属性表示 Object 的原型对象。

3.例子

如下例子说明实例dog的原型链:即
dog–>Animal.prototype – > Object.prototype – > null
实例对象dog的原型对象__proto__就是指向构造函数Animal的prototype属性,而prototype是一个对象,也拥有内部属性__proto_,因此指向构造函数Object的prototype属性。所有属性、方法和行为会层层向上直到一个对象的原型对象为 null为止。

class Animal {
    
    
    name:string;
    constructor(name:string="Animal") {
    
    
      this.name = name;  
    }
    sayHello(){
    
    
        console.log("Hello ",this.name);
    }
}
console.log(typeof(Animal.prototype)) // object

let dog = new Animal("Dog")
console.log(Object.getPrototypeOf(dog) === Animal.prototype) // true
console.log(Object.getPrototypeOf(Animal.prototype) === Object.prototype) // true
console.log(Object.getPrototypeOf(Object.prototype) === null) // true

二、深入了解prototype

1.Function 对象

每个被函数声明(function)创建的函数是一个 Function 对象,具有 Function 对象的所有属性、方法和行为。我们查看Function的声明文件,首先它被定义成一个FunctionConstructor的类型的对象类型接口,其中包含2个方法,它们都返回一个叫做Function类型的对象类型接口,和一个只读的prototype的Function对象类型接口的属性。

interface FunctionConstructor {
    
    
    /**
     * Creates a new function.
     * @param args A list of arguments the function accepts.
     */
    new(...args: string[]): Function;
    (...args: string[]): Function;
    readonly prototype: Function;
}

declare var Function: FunctionConstructor;

2.Function.prototype

Function.prototype即FunctionConstructor.prototype,是Function对象类型接口,Function对象类型接口也是一个对象Object.几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。每个对象Object都定义了一个构造函数constructor。

interface Object {
    
    
    /** The initial value of Object.prototype.constructor is the standard built-in Object constructor. */
    constructor: Function;
    ...
}
/**
 * Creates a new function.
 */
interface Function {
    
    
    /**
     * Calls the function, substituting the specified object for the this value of the function, and the specified array for the arguments of the function.
     * @param thisArg The object to be used as the this object.
     * @param argArray A set of arguments to be passed to the function.
     */
    apply(this: Function, thisArg: any, argArray?: any): any;

    /**
     * Calls a method of an object, substituting another object for the current object.
     * @param thisArg The object to be used as the current object.
     * @param argArray A list of arguments to be passed to the method.
     */
    call(this: Function, thisArg: any, ...argArray: any[]): any;

    /**
     * For a given function, creates a bound function that has the same body as the original function.
     * The this object of the bound function is associated with the specified object, and has the specified initial parameters.
     * @param thisArg An object to which the this keyword can refer inside the new function.
     * @param argArray A list of arguments to be passed to the new function.
     */
    bind(this: Function, thisArg: any, ...argArray: any[]): any;

    /** Returns a string representation of a function. */
    toString(): string;

    prototype: any;
    readonly length: number;

    // Non-standard extensions
    arguments: any;
    caller: Function;
}

3.实例

如下例子说明function Animal的原型链:即
Animal->Function.prototype – > Object.prototype – > null
实例方法Animal的原型对象__proto__就是指向构造函数Function的prototype属性,而prototype是一个对象,也拥有内部属性__proto_,因此指向构造函数Object的prototype属性。

class Animal {
    
    
    name:string;
    constructor(name:string="Animal") {
    
    
      this.name = name;  
    }
    sayHello(){
    
    
        console.log("Hello ",this.name);
    }
}

console.log(typeof Animal); // function
console.log(Object.getPrototypeOf(Animal) === Function.prototype) // true
console.log(Object.getPrototypeOf(Function.prototype) === Object.prototype) // true
console.log(Object.getPrototypeOf(Object.prototype) === null) // true

4.prototype 和 Object.getPrototypeOf()的区别

你可能已经注意到我们的 function Animal 有一个叫做 prototype 的特殊属性。该特殊属性可与 JavaScript 的 new 操作符一起使用。对原型对象的引用被复制到新实例的内部__proto__属性。例如,当执行 var animal = new Animal(); 时,JavaScript(在内存中创建对象之后,和在运行函数 Animal() 把 this 指向对象之前)设置 animal.__proto__= Animal.prototype;。然后当您访问实例的属性时,JavaScript 首先会检查它们是否直接存在于该对象上,如果不存在,则会__proto__ 中查找。这意味着你在 prototype 中定义的所有内容都可以由所有实例有效地共享,你甚至可以稍后更改部分 prototype,并在所有现有实例中显示更改(如果有必要的话)。

像上面的例子中,如果你执行 var a1 = new Animal(); var a2 = new Animal(); 那么 a1.sayHello() 事实上会指向 Object.getPrototypeOf(a1).sayHello(),它就是你在 Animal.prototype.sayHello 中定义的内容。也就是说:Object.getPrototypeOf(a1).sayHello == Object.getPrototypeOf(a2).sayHello == Animal.prototype.sayHello(补充:实际上,执行 a1.sayHello() 相当于执行 Object.getPrototypeOf(a1).sayHello.call(a1)==Animal.prototype.sayHello.call(a1))

简而言之, prototype 是用于类的,而 Object.getPrototypeOf() 是用于实例的(instances),两者功能一致。

三、原型链的作用

继承

在ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。

class Parent {
    
    }

class Child extends Parent {
    
    
  constructor() {
    
    
    super();
  }
}

注意,super虽然代表了父类Parent的构造函数,但是返回的是子类Child的实例,即super内部的this指的是Child的实例,因此super()在这里相当Parent.prototype.constructor.call(this)。子类Child的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。

原型链图示:
在这里插入图片描述

1.属性继承

JavaScript 对象是动态的属性“包”(指其自己的属性)。JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

实例

	// 让我们从一个函数里创建一个对象o,它自身拥有属性a和b的:
	let f = function () {
    
    
	   this.a = 1;
	   this.b = 2;
	}
	/* 这么写也一样
	function f() {
	  this.a = 1;
	  this.b = 2;
	}
	*/
	let o = new f(); // {a: 1, b: 2}
	
	// 在f函数的原型上定义属性
	f.prototype.b = 3;
	f.prototype.c = 4;
	
	// 不要在 f 函数的原型上直接定义 f.prototype = {b:3,c:4};这样会直接打破原型链
	// o.[[Prototype]] 有属性 b 和 c
	//  (其实就是 o.__proto__ 或者 o.constructor.prototype)
	// o.[[Prototype]].[[Prototype]] 是 Object.prototype.
	// 最后o.[[Prototype]].[[Prototype]].[[Prototype]]是null
	// 这就是原型链的末尾,即 null,
	// 根据定义,null 就是没有 [[Prototype]]。
	
	// 综上,整个原型链如下:
	
	// {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype---> null
	
	console.log(o.a); // 1
	// a是o的自身属性吗?是的,该属性的值为 1
	
	console.log(o.b); // 2
	// b是o的自身属性吗?是的,该属性的值为 2
	// 原型上也有一个'b'属性,但是它不会被访问到。
	// 这种情况被称为"属性遮蔽 (property shadowing)"
	
	console.log(o.c); // 4
	// c是o的自身属性吗?不是,那看看它的原型上有没有
	// c是o.[[Prototype]]的属性吗?是的,该属性的值为 4
	
	console.log(o.d); // undefined
	// d 是 o 的自身属性吗?不是,那看看它的原型上有没有
	// d 是 o.[[Prototype]] 的属性吗?不是,那看看它的原型上有没有
	// o.[[Prototype]].[[Prototype]] 为 null,停止搜索
	// 找不到 d 属性,返回 undefined

2.方法继承

JavaScript 并没有其他基于类的语言所定义的“方法”。在 JavaScript 里,任何函数都可以添加到对象上作为对象的属性。函数的继承与其他的属性继承没有差别,包括上面的“属性遮蔽”(这种情况相当于其他语言的方法重写)

实例

	var o = {
    
    
	  a: 2,
	  m: function(){
    
    
	    return this.a + 1;
	  }
	};
	
	console.log(o.m()); // 3
	// 当调用 o.m 时,'this' 指向了 o.
	
	var p = Object.create(o);
	// p是一个继承自 o 的对象
	
	p.a = 4; // 创建 p 的自身属性 'a'
	console.log(p.m()); // 5
	// 调用 p.m 时,'this' 指向了 p
	// 又因为 p 继承了 o 的 m 函数
	// 所以,此时的 'this.a' 即 p.a,就是 p 的自身属性 'a' 

四、原型链的性能

在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。
遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性,则必须使用所有对象从 Object.prototype 继承的 hasOwnProperty 方法。
下面给出一个具体的例子来说明它:

	class A {
    
    
		name:string
		constructor(name:sting="defalut"){
    
    
			this.name = name
		}	
	}
	
	let a = new A()
	console.log(a.hasOwnProperty('name'));
	// true
	
	console.log(a.hasOwnProperty('abc'));
	// false
	
	console.log(a.hasOwnProperty('efg'));
	// false

注意:检查属性是否为 undefined 是不能够检查其是否存在的。该属性可能已存在,但其值恰好被设置成了 undefined。hasOwnProperty 和Object.keys()是 JavaScript 中唯处理属性并且不会遍历原型链的方法。


总结

文章介绍了构造函数原型对象prototype,以及被它创建的实例对象(instance)的__proto__属性,获取实例__proto__属性的方法Object.getPrototypeOf()。解释了两者的区别以及使用。当实例查找某个属性和方法时候,就根据__proto__属性链条层层向上查询,直到null,但是遍历整个原型链会产生性能问题,使用hasOwnProperty()和Object.keys()判断属性是否在改对象实例中,这样就避免了遍历整个原型链。

猜你喜欢

转载自blog.csdn.net/Suarez1987/article/details/112531456
今日推荐