JavaScript原型和原型链

一、引用类型皆为对象

原型和原型链都是来源于对象而服务于对象的概念,所以我们要先明确一点:

JavaScript中一切引用类型都是对象,对象就是属性的集合。

Array类型Function类型Object类型Date类型RegExp类型 等都是引用类型。

也就是说 数组是对象、函数是对象、正则是对象、对象还是对象。

	 const arr = [];
	 const obj = {};
	 const fn = function () {};
	 console.log(arr instanceof Object); // true
	 console.log(obj instanceof Object); // true
	 console.log(fn instanceof Object); // true

二、原型和原型链是什么

上面我们说到对象就是属性(property)的集合,方法也是一种属性,因为它也是键值对的表现形式,具体如下:

	const obj = {};
	obj.say = function () {
	  console.log("say!");
	};
	console.log(obj.hasOwnProperty("say"));

可以看到obj上确实多了一个say的属性,值为一个函数,但是问题来了,obj上面并没有hasOwnProperty这个方法,为什么我们可以调用呢?这就引出了 原型

每一个对象从被创建开始就和另一个对象关联,从另一个对象上继承其属性,这个另一个对象就是 原型

当访问一个对象的属性时,先在对象的本身找,找不到就去对象的原型上找,如果还是找不到,就去对象的原型(原型也是对象,也有它自己的原型)的原型上找,如此继续,直到找到为止,或者查找到最顶层的原型对象中也没有找到,就结束查找,返回undefined这条由对象及其原型组成的链就叫做原型链

总结一下:

  • 原型存在的意义就是组成原型链:引用类型皆对象,每个对象都有原型,原型也是对象,也有它自己的原型,一层一层,组成原型链。
  • 原型链存在的意义就是继承:访问对象属性时,在对象本身找不到,就在原型链上一层一层找。说白了就是一个对象可以访问其他对象的属性。
  • 继承存在的意义就是属性共享:好处有二:一是代码重用;二是可扩展,不同对象可能继承相同的属性,也可以定义只属于自己的属性。

三、创建对象

对象的创建方式主要有两种,一种是new操作符后跟函数调用,另一种是字面量表示法。

目前我们现在可以理解为:所有对象都是由new操作符后跟函数调用来创建的,字面量表示法只是语法糖(即本质也是new,功能不变,使用更简洁)。

	// new操作符后跟函数调用
	let obj = new Object()
	let arr = new Array()
	
	// 字面量表示法
	let obj = { a: 1}
	// 等同于
	let obj = new Object()
	obj.a = 1
	
	let arr = [1,2]
	// 等同于
	let arr = new Array()
	arr[0] = 1
	arr[1] = 2

ObjectArray等称为构造函数,不要怕这个概念,构造函数和普通函数并没有什么不同,只是由于这些函数常被用来跟在new后面创建对象。new后面调用一个空函数也会返回一个对象,任何一个函数都可以当做构造函数。

所以构造函数更合理的理解应该是函数的构造调用

NumberStringBooleanArrayObjectFunctionDateRegExpError这些都是函数,而且是原生构造函数,在运行时会自动出现在执行环境中。

构造函数是为了创建特定类型的对象,这些通过同一构造函数创建的对象有相同原型,共享某些方法。举个例子,所有的数组都可以调用push方法,因为它们有相同原型。

实现一个构造函数:

	// 惯例,构造函数应以大写字母开头
	function Person(name) {
	  // 函数内this指向构造的对象
	  // 构造一个name属性
	  this.name = name
	  // 构造一个sayName方法
	  this.sayName = function() {
	    console.log(this.name)
	  }
	}
	
	// 使用自定义构造函数Person创建对象
	let person = new Person('logan')
	person.sayName() // 输出:logan

总结一下:

  • 构造函数用来创建对象,同一构造函数创建的对象,其原型相同。

四、__proto__prototype

每个对象都有原型,那么我们怎么获取到一个对象的原型呢?那就是对象的__proto__属性,指向对象的原型(隐式原型)。

__proto__属性虽然在ECMAScript 6语言规范中标准化,但是不推荐被使用,现在更推荐使用Object.getPrototypeOfObject.getPrototypeOf(obj)也可以获取到obj对象的原型。本文中使用__proto__只是为了便于理解。

	Object.getPrototypeOf(person) === person.__proto__ // true

每个函数拥有prototype属性,指向使用new操作符和该函数创建的对象实例的原型对象。

	Person.prototype === person.__proto__ // true

通过将公共属性和方法定义在原型上,所有实例对象可共享,避免重复创建。

	Person.prototype.say = function() { console.log("Hello"); };
	const p1 = new Person();
	const p2 = new Person();
	console.log(p1.say === p2.say); // true

总结一下:

  • 对象有__proto__属性,函数有__proto__属性,数组也有__proto__属性,只要是引用类型,就有__proto__属性,指向其原型。
  • 只有函数有prototype属性,指向new操作符加调用该函数创建的对象实例的原型对象。

五、原型链顶层

原型链之所以叫原型链,而不叫原型环,说明它是有始有终的,那么原型链的顶层是什么呢?

拿我们的person对象来看,它的原型对象,很简单

	// 1. person的原型对象
	person.__proto__ === Person.prototype
	// 2. Person.prototype的原型对象
	Person.prototype.__proto__ === Object.prototype
	// 3.Object.prototype也是一个对象,那么它的原型呢?这里比较特殊,切记!!!
	Object.prototype.__proto__ === null

我们就可以换个方式描述下 原型链 :由对象的__proto__属性串连起来的直到Object.prototype.__proto__(为null)的链就是原型链。

六、constructor

构造函数都有一个prototype属性,指向使用这个构造函数创建的对象实例的原型对象。

这个原型对象中默认有一个constructor属性,指回该构造函数。

	Person.prototype.constructor === Person // true

七、函数对象的原型链

之前提到过引用类型皆对象,函数也是对象,那么函数对象的原型链是怎么样的呢?

对象都是被构造函数创建的,函数对象的构造函数就是Function,注意这里F是大写

	let fn = function() {}
	// 函数(包括原生构造函数)的原型对象为Function.prototype
	fn.__proto__ === Function.prototype // true
	Array.__proto__ === Function.prototype // true
	Object.__proto__ === Function.prototype // true

Function.prototype也是一个普通对象,所以Function.prototype.__proto__ === Object.prototype

这里有一个特例,Function__proto__属性指向Function.prototype

函数都是由Function原生构造函数创建的,所以函数的__proto__属性指向Function的prototype属性

八、总结

在这里插入图片描述
知识点

  • 引用类型都是对象,每个对象都有原型对象。
  • 对象都是由构造函数创建,对象的__proto__属性指向其原型对象,构造函数的prototype属性指向其创建的对象实例的原型对 象,所以对象的__proto__属性等于创建它的构造函数的prototype属性。
  • 所有通过字面量表示法创建的普通对象的构造函数为Object
  • 所有原型对象都是普通对象,构造函数为Object
  • 所有函数的构造函数是Function
  • Object.prototype没有原型对象
	// f1、f2都是通过new Foo()创建的对象,构造函数为Foo,所以有
	f1.__proto__ === Foo.prototype
	// Foo.prototype为普通对象,构造函数为Object,所以有
	Foo.prototype.__proto === Object.prototype
	// Object.prototype没有原型对象
	Object.prototype.__proto__ === null


	// Foo是个函数对象,构造函数为Function
	Foo.__proto__ === Function.prototype
	// Function.prototype为普通对象,构造函数为Object,所以有
	Function.prototype.__proto__ === Object.prototype


	// o1、o2构造函数为Object
	o1.__proto__ === Object.prototype


	// 原生构造函数也是函数对象,其构造函数为Function
	Object.__proto__ === Function.prototype
	// 特例
	Function.__proto__ === Function.prototype

九、new操作符

当我们使用new时,做了些什么?

  1. 创建一个全新对象,并将其__proto__属性指向构造函数的prototype属性。
  2. 将构造函数调用的this指向这个新对象,并执行构造函数。
  3. 如果构造函数返回对象类型Object(包含Functoin, Array, Date, RegExg, Error等),则正常返回,否则返回这个新的对象。

模拟实现一下:

	function newOperator(func, ...args) {
	    if (typeof func !== 'function') {
	        console.error('第一个参数必须为函数,您传入的参数为', func)
	        return
	    }
	    // 创建一个全新对象,并将其`__proto__`属性指向构造函数的`prototype`属性
	    let newObj = Object.create(func.prototype)
	    // 将构造函数调用的this指向这个新对象,并执行构造函数
	    let result = func.apply(newObj, args)
	    // 如果构造函数返回对象类型Object,则正常返回,否则返回这个新的对象
	    return (result instanceof Object) ? result : newObj
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值