学习记录_0909

原型、原型链

https://juejin.im/post/6844903749345886216

一、引用类型皆为对象
JavaScript中一切引用类型都是对象,对象就是属性的集合。
Array类型、Function类型、Object类型、Data类型、RegExp类型等都是引用类型。
也就是说 数组是对象,函数是对象,正则是对象,对象还是对象。

let arr = []
let fn = function () {}
let obj = {}
arr instanceof Object //  true
fn instanceof Object  //  true
obj instanceof Object  //  true

二、原型和原型链

let obj = {}
obj.sayHello = function () { alart("hello") }
obj.hasOwnProperty('sayHello')
obj  //  {sayHello:f}

每一个对象从创建开始就和另一个对象关联,从另一个对象上继承其属性,这个另一个对象就是原型。
当访问一个对象的属性时,先在对象的本身找,找不到就去对象的原型上找,如果还找不到就去对象的原型的原型上找(原型也是对象,本身也有自己的原型),如此继续,知道找到为止,或者查找到最顶层的原型对象中也没有找到,就结束查找,返回 undefined。
这条由对象及其原型组成的链就叫做原型链。
这样就解释了为什么数组可以使用 push、slice 等方法,函数可以使用 call、bind 等方法,因为在他们的原型链上找到了对应的方法。
总结:

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

三、创建对象

创建方式有两种,一种是 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

Object、Array等称为构造函数,与普通函数没有区别,只是常用于跟在 new 后面创建对象。new 后面调用一个空函数也会返回一个对象,任何一个函数都可以当构造函数。
构造函数更合理的理解是函数的构造调用。
Number、String、Boolean、Array、Object、Function、Date、RegExp、Error 这些都是函数,而且是原生构造函数,在运行时会自动出现在执行环境中。
构造函数是为了创建特定类型的对象,这些通过同一构造函数创建的对象有相同原型,共享某些方法,比如所有数组都可以调用 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 _ 属性,对象有 _ proto _ 属性,函数有 _ proto _ 属性,数组也有 _ proto _ 属性,只要是引用类型,就有 _ proto _ 属性,都指向他们各自的原型对象。
_ proto _ 在es6中规范化,不推荐使用,更推荐使用 Object.getPrototypeOf,Object.getPrototypeOf(obj)也可以获取obj的原型。

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

构造函数是为了创建特定类型的对象,那如果想让 person 这个构造函数创建的对象都共享一个方法,
错误示范:

// 调用构造函数Person创建一个新对象personA
let personA = new Person('张三')
// 在personA的原型上添加一个方法,以供之后Person创建的对象所共享
personA._proto_.eat = function() {
	console.log('吃东西')
}
let personB = new Person("李四")
personB.eat()  //  吃东西

每次修改一类对象的原型对象,都去创建一个新的对象实例,然后访问其原型对象并添加或修改属性总觉得多此一举。既然构造函数创建的对象实例的原型对象都是同一个,那么构造函数和其构造出的对象实例的原型对象之间有联系就好了,这个联系就是 prototype,每个函数拥有 prototype 属性,指向 new 操作符和该函数创建的对象实例的原型对象。

Person.prototype === personA._proto_  //  true

正确示范:

Person.prototype.drink = function() {
	console.log("喝东西")
}
let personA = new Person("张三")
personB.drink()  //  喝东西

总结:

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

五、原型链顶层

//  1. person的原型对象
person._proto_ === Person.prototype
// 2. Person.prototype的原型对象
Person.prototype._proto_ === Object.prototype
//  3.Object.prototype
Object.prototype === null

*原型链:*由对象的 _ proto_ 属性串联起来的直到 Object.prototype._ proto_(null) 的链就是原型链。
根据以上模拟js读取对象属性:

function getProperty(obj,propName) {
	//  在对象本身查找
	if(obj.hasOwnProperty(propName)) {
		return obj[propName]
	}else if(obj._proto_ !== null) {
		// 如果对象有原型,则在原型上递归查找
		return getProperty(obj._proto_,propName)
	}else {
		//  直到找到Object.prototype,Object.prototype.__proto__为null,返回undefined
		return undefined
	}
}

六、constructor

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

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

在这里插入图片描述
七、函数对象的原型链
对象都是被构造函数创建的,函数也是对象,函数对象的构造函数就是 Function。

let fn = function() {}
fn._proto_ === Function.prototype  //true
//  Function.prototype也是一个普通对象
Function.prototype._proto_ === Object.prototype  //  true
//  特例
Function._proto_ === Function.prototype

总结:函数都是由Function原生构造函数创建的,所以函数的 _ proto_ 属性指向Function的prototype属性。

let f1 = new Foo('chen')
let o1 = new Object()
function Foo(name){
	this.name = name
}

已知:

  1. 引用类型都是对象,每个对象都有原型对象
  2. 对象都是由其构造函数创建,对象的 _ proto_属性指向其原型对象,构造函数的prototype属性指向其创建的对象实例的原型对象,所以对象的_ proto_属性等于创建他的构造函数的prototype属性
  3. 所有字面量表示法创建的普通对象的构造函数为Object
  4. 所有原型对象都是普通对象,构造函数为Object
  5. 所有函数的构造函数是Function
  6. Object.prototype 没有原型对象
// f1是通过 new Foo() 创建的函数,其构造函数是 Foo(),所以
f1._proto === Foo.prototype
// Foo.prototype为普通对象,构造函数为Object,所以
Foo.prototype._proto_ === Object.prototype
//  Object.prototype没有原型对象
Object.prototype._proto_ === null

// o1的构造函数为Object
o1._proto_ === Object.prototype

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

instanceof操作符

判断一个变量的类型可以使用 typeof 运算符,但是并不适用于引用类型,除了函数对象会返回 function 以为,其他都返回 object。想知道一个对象的具体类型,就要用到 instanceof。

instanceof运算符用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置

instanceof操作符左边是一个对象,右边是一个构造函数,在左边对象的原型链上查找,知道找到右边构造函数的prototype属性就返回true,或者查到顶层null,(Object.prototype._proto _),返回false。
模拟实现:

function instanceOf(obj,Constructor) {  //  obj代表左边对象,Constructor代表右边的构造函数
	let right = Constructor.prototype  //  去构造函数显示原型
	let left = obj._proto_  //  取对象隐式原型
	//  到达原型链顶层还未找到则返回false
	if(right === null) {
		return false	
	}
	//  对象实例的隐式原型等于构造函数的显式原型则返回true
	if(left === right) {
		return true
	}
	查找原型链shangyuc
	return(obj._proto_,Constructor)
}

fn instanceof Object //true
// 1. fn.__proto__ === Function.prototype
// 2. fn.__proto__.__proto__ === Function.prototype.__proto__ === Object.prototype
arr instanceof Object //true
// 1. arr.__proto__ === Array.prototype
// 2. arr.__proto__.__proto__ === Array.prototype.__proto__ === Object.prototype
Object instanceof Object // true
// 1. Object.__proto__ === Function.prototype
// 2. Object.__proto__.__proto__ === Function.prototype.__proto__ === Object.prototype
Function instanceof Function // true
Function instanceof Function // true
// Function.__proto__ === Function.prototype

总结:instanceof运算符用于检查右边构造函数的prototype属性是否出现在左边原型链中的任何位置。表示的是一种原型链继承的关系。

object.create

es5提供的Object.create()方法,会创建一个新对象,第一个参数接受一个对象,将会作为新创建对象的原型对象,第二个可选参数是属性描述符(不常用,默认undefined)
模拟:

function createObj(proto){
	function F(){}
	F.prototype = proto
	return new F()
}

如果想要生成一个不继承任何属性的对象,可以使用Object.create(null)。
如果想要生成一个平常字面量方法生成的对象,需要将其原型对象指向Object.prototype

let obj = Object.create(Object.prototype)
//  等价于
let obj = {}

new操作符

使用new时,发生了:

  1. 创建一个全新对象,并将其_ proto_属性指向构造函数的prototype属性。
  2. 将构造函数调用的this指向这个新对象,并执行构造函数
  3. 如果构造函数返回对象类型Object(包含Functoin, Array, Date, RegExg, Error等),则正常返回,否则返回这个新的对象。
function newOperator(func,...args) {
	if(typeof func!== 'function') {
		console.log("第一个参数必须为函数,您传入的参数为",func)
		return
	}
	// 创建一个全新对象,并将其`__proto__`属性指向构造函数的`prototype`属性
	let newObj = Object.create(func.prototype)
	// 将构造函数调用的this指向这个新对象,并执行构造函数
	let result = func.apply(newObj,args)
	// 如果构造函数返回对象类型Object,则正常返回,否则返回这个新的对象
    return (result instanceof Object) ? result : newObj
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值