Javascript 继承与原型链 __proto__ 和 prototype

有没有真正的了解过Javascript的 proto 和 prototype ? 它们在起什么重要的作用?

在 ES2015/ES6 中引入了 class 关键字,但那只是语法糖,JavaScript 仍然是基于原型的 本身不提供一个 class 实现

基于原型链的继承

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

          let func = function(){
            this.a = 1;
            this.b = 2; 
        }

        func.prototype.b = 3;
        func.prototype.c = 4;

        let o = new func();

        //整个原型链如下:
        // {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
    给对象设置属性会创建自有属性。获取和设置属性的唯一限制是内置 getter 或 setter 的属性。

继承方法

        let f = function(){
            this.a = 1; 
            add(){
                console.log(" value ", this.a + 1);
            }
        }
        console.log(f.add()); // 2
        // 当调用 f.add 时,'this' 指向了 f.


        let o = Object.create(f);
        // o是一个继承自 f 的对象
        o.a = 2; // 创建 o 的自身属性 'a'
        o.add(); //3
        // 调用 o.add 时,'this' 指向了 o
        // 又因为 o 继承了 f 的 m 函数
        // 所以,此时的 'this.a' 即 o.a,就是 o 的自身属性 'a' 

在 JavaScript 中使用原型

   function doSomething(){}
   console.log(doSomething.prototype);

输出内容:

	{constructor: ƒ}
	constructor: ƒ doSomething()
	__proto__:
	constructor: ƒ Object()
	hasOwnProperty: ƒ hasOwnProperty()
	isPrototypeOf: ƒ isPrototypeOf()
	propertyIsEnumerable: ƒ propertyIsEnumerable()
	toLocaleString: ƒ toLocaleString()
	toString: ƒ toString()
	valueOf: ƒ valueOf()
	__defineGetter__: ƒ __defineGetter__()
	__defineSetter__: ƒ __defineSetter__()
	__lookupGetter__: ƒ __lookupGetter__()
	arguments: (...)
	caller: (...)
	length: 1
	name: "__lookupGetter__"
	__proto__: ƒ ()
	[[Scopes]]: Scopes[0]
	__lookupSetter__: ƒ __lookupSetter__()
	get __proto__: ƒ __proto__()
	set __proto__: ƒ __proto__()

这个时候我们可以给doSomething函数的原型对象添加新属性,如下:

	function doSomething(){}
	doSomething.prototype.foo = "bar";
	console.log(doSomething.prototype);

输出如下:

	{foo: "bar", constructor: ƒ}
	foo: "bar"
	constructor: ƒ doSomething()
	arguments: null
	caller: null
	length: 0
	name: "doSomething"
	prototype: {foo: "bar", constructor: ƒ}
	__proto__: ƒ ()
	[[FunctionLocation]]: VM1773:1
	[[Scopes]]: Scopes[2]
	__proto__:
	constructor: ƒ Object()
	hasOwnProperty: ƒ hasOwnProperty()
	isPrototypeOf: ƒ isPrototypeOf()
	propertyIsEnumerable: ƒ propertyIsEnumerable()
	toLocaleString: ƒ toLocaleString()
	toString: ƒ toString()
	valueOf: ƒ valueOf()
	__defineGetter__: ƒ __defineGetter__()
	__defineSetter__: ƒ __defineSetter__()
	__lookupGetter__: ƒ __lookupGetter__()
	__lookupSetter__: ƒ __lookupSetter__()
	get __proto__: ƒ __proto__()
	set __proto__: ƒ __proto__()

现在我们可以通过new操作符来创建基于这个原型对象的doSomething实例。使用new操作符,只需在调用doSomething函数语句之前添加new。这样,便可以获得这个函数的一个实例对象。一些属性就可以添加到该原型对象中。

	function doSomething(){}
	doSomething.prototype.foo = "bar"; // add a property onto the     prototype
	let doSomeInstancing = new doSomething();
	doSomeInstancing.prop = "some value"; // add a property onto the object
	console.log( doSomeInstancing );
	//log
	doSomeInstancing {prop: "some value"}
	{
		prop: "some value"
		__proto__:
			foo: "bar"
			constructor: ƒ doSomething()
			__proto__:
			constructor: ƒ Object()
			hasOwnProperty: ƒ hasOwnProperty()
			isPrototypeOf: ƒ isPrototypeOf()
			propertyIsEnumerable: ƒ propertyIsEnumerable()
			toLocaleString: ƒ toLocaleString()
			toString: ƒ toString()
			valueOf: ƒ valueOf()
			__defineGetter__: ƒ __defineGetter__()
			__defineSetter__: ƒ __defineSetter__()
			__lookupGetter__: ƒ __lookupGetter__()
			__lookupSetter__: ƒ __lookupSetter__()
			get __proto__: ƒ __proto__()
			set __proto__: ƒ __proto__()
	}

如上所示, doSomeInstancing 中的__proto__是 doSomething.prototype. 但这是做什么的呢?当你访问doSomeInstancing 中的一个属性,浏览器首先会查看doSomeInstancing 中是否存在这个属性。

如果 doSomeInstancing 不包含属性信息, 那么浏览器会在 doSomeInstancing 的 proto 中进行查找(同 doSomething.prototype). 如属性在 doSomeInstancing 的 proto 中查找到,则使用 doSomeInstancing 中 proto 的属性。

否则,如果 doSomeInstancing 中 proto 不具有该属性,则检查doSomeInstancing 的 protoproto 是否具有该属性。默认情况下,任何函数的原型属性 proto 都是 window.Object.prototype. 因此, 通过doSomeInstancing 的 protoproto ( 同 doSomething.prototype 的 proto (同 Object.prototype)) 来查找要搜索的属性。

如果属性不存在 doSomeInstancing 的 protoproto 中, 那么就会在doSomeInstancing 的 protoprotoproto 中查找。然而, 这里存在个问题:doSomeInstancing 的 protoprotoproto 其实不存在。因此,只有这样,在 proto 的整个原型链被查看之后,这里没有更多的 proto , 浏览器断言该属性不存在,并给出属性值为 undefined 的结论。

	function doSomething(){}
	doSomething.prototype.foo = "bar";
	var doSomeInstancing = new doSomething();
	doSomeInstancing.prop = "some value";
	console.log("doSomeInstancing.prop:      " + doSomeInstancing.prop);
	console.log("doSomeInstancing.foo:       " + doSomeInstancing.foo);
	console.log("doSomething.prop:           " + doSomething.prop);
	console.log("doSomething.foo:            " + doSomething.foo);
	console.log("doSomething.prototype.prop: " + doSomething.prototype.prop);
	console.log("doSomething.prototype.foo:  " + doSomething.prototype.foo);

	doSomeInstancing.prop:      some value
	doSomeInstancing.foo:       bar
	doSomething.prop:           undefined
	doSomething.foo:            undefined
	doSomething.prototype.prop: undefined
	doSomething.prototype.foo:  bar

使用不同的方法来创建对象和生成原型链

使用语法结构创建的对象
	var o = {a: 1};
	
	// o 这个对象继承了 Object.prototype 上面的所有属性
	// o 自身没有名为 hasOwnProperty 的属性
	// hasOwnProperty 是 Object.prototype 的属性
	// 因此 o 继承了 Object.prototype 的 hasOwnProperty
	// Object.prototype 的原型为 null
	// 原型链如下:
	// o ---> Object.prototype ---> null
	
	var a = ["yo", "whadup", "?"];
	
	// 数组都继承于 Array.prototype 
	// (Array.prototype 中包含 indexOf, forEach 等方法)
	// 原型链如下:
	// a ---> Array.prototype ---> Object.prototype ---> null
	
	function f(){
	  return 2;
	}
	
	// 函数都继承于 Function.prototype
	// (Function.prototype 中包含 call, bind等方法)
	// 原型链如下:
	// f ---> Function.prototype ---> Object.prototype ---> null

使用构造器创建的对象

在 JavaScript 中,构造器其实就是一个普通的函数。当使用 new 操作符 来作用这个函数时,它就可以被称为构造方法(构造函数)。

function Graph() {
  this.vertices = [];
  this.edges = [];
}

Graph.prototype = {
  addVertex: function(v){
    this.vertices.push(v);
  }
};

var g = new Graph();
// g 是生成的对象,他的自身属性有 'vertices' 和 'edges'。
// 在 g 被实例化时,g.[[Prototype]] 指向了 Graph.prototype。

使用 class 关键字创建的对象

ECMAScript6 引入了一套新的关键字用来实现 class。使用基于类语言的开发人员会对这些结构感到熟悉,但它们是不同的。JavaScript 仍然基于原型。这些新的关键字包括 class, constructor,static,extends 和 super。

class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

class Square extends Polygon {
  constructor(sideLength) {
    super(sideLength, sideLength);
  }
  get area() {
    return this.height * this.width;
  }
  set sideLength(newLength) {
    this.height = newLength;
    this.width = newLength;
  }
}

var square = new Square(2);

性能

在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。

遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性,则必须使用所有对象从 Object.prototype 继承的 hasOwnProperty 方法。下面给出一个具体的例子来说明它:

console.log(g.hasOwnProperty('vertices'));
// true

console.log(g.hasOwnProperty('nope'));
// false

console.log(g.hasOwnProperty('addVertex'));
// false

console.log(g.__proto__.hasOwnProperty('addVertex'));
// true

hasOwnProperty 是 JavaScript
中唯一一个处理属性并且不会遍历原型链的方法。(译者注:原文如此。另一种这样的方法:Object.keys())

注意:检查属性是否为 undefined 是不能够检查其是否存在的。该属性可能已存在,但其值恰好被设置成了 undefined。

深入了解Javascript原型链及用法
强力推荐 Developer : https://developer.mozilla.org

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值