JS——原型与原型链

讲原型的时候,我们应该先要记住以下几个要点,这几个要点是理解原型的关键
1、所有的引用类型(数组、函数、对象)可以自由扩展属性(除null以外)。
2、所有的引用类型都有一个 ’_ _ proto_ _'属性(也叫隐式原型,它是一个普通的对象)。
3、所有的函数都有一个**’prototype’属性**(这也叫显式原型,它也是一个普通的对象)。
4、所有引用类型,它的 ’_ _ proto_ _'属性指向它的构造函数的’prototype’属性
5、当试图得到一个对象的属性时,如果这个对象本身不存在这个属性,那么就会去它的’_ _ proto_ _'属性(也就是它的构造函数的’prototype’属性)中去寻找。

原型

举个例子:

//这是一个构造函数
function Foo(name, age) {
  this.name = name
  this.age = age
}
// 所有的函数都有一个prototype属性,这个属性是一个对象
// 所有的对象可以自由扩展属性
// 则就有了以下写法
Foo.prototype = {
  // prototype对象里面又有其他的属性
  showName() {
    console.log("I'm " + this.name) // this是什么要看执行的时候谁调用了这个函数
  },
  showAge() {
    console.log("I'm " + this.age) // this是什么要看执行的时候谁调用了这个函数
  }
}
var fn = new Foo('小明', 19)
// 当试图得到一个对象的属性时,如果这个对象本身不存在这个属性,那么就会去它构造函数的'prototype'属性中去找
fn.showName() // I'm 小明
fn.showAge() // I'm 19

为什么要使用原型呢?

试想如果我们要通过Foo()来创建很多很多个对象,如果我们是这样子写的话:

function Foo(name, age) {
  this.name = name
  this.age = age
  this.showName = function() {
    console.log("I'm " + this.name)
  }
  this.showAge = function() {
    console.log("I'm " + this.age)
  }
}

这样,我们创建出来的每一个对象,里面都有showName和showAge方法,这样就会占用很多的资源

而通过原型来实现的话,只需要在构造函数里面给属性赋值,而把方法写在Foo.prototype属性(这个属性是唯一的)里面。这样每个对象都可以使用prototype属性里面的showName、showAge方法,并且节省了不少的资源

function Foo(name, age) {
  this.name = name
  this.age = age
}

Foo.prototype = {
  showName() {
    console.log("I'm " + this.name)
  },
  showAge() {
    console.log("I'm " + this.age)
  }
}

原型链

根据原型的要点5:当试图得到一个对象的属性时,如果这个对象本身不存在这个属性,那么就会去它构造函数的’prototype’属性中去寻找。
那是因为 ’prototype’属性是一个对象,所以它也有一个’_ _ proto_ _'属性

再举个例子:

// 构造函数
function Foo(name, age) {
  this.name = name
  this.age=age
}
Object.prototype.toString = function() {
  //this是什么要看执行的时候谁调用了这个函数。
  console.log("I'm "+this.name + " I'm " + this.age)
}
var fn = new Foo('小明', 19)
fn.toString() // I'm 小明 I'm 19
console.log(fn.toString === Foo.prototype.__proto__.toString) //true
console.log(fn.__proto__ === Foo.prototype) //true
console.log(Foo.prototype.__proto__ === Object.prototype) //true
console.log(Object.prototype.__proto__ === null) //true

在这里插入图片描述
所有引用类型,它的 ’_ _ proto_ _'属性指向它的构造函数的’prototype’属性。所以fn.__proto === Foo.prototype
又因为Foo.prototype是一个普通的对象,它的构造函数是Object
则又有Foo.prototype.__proto__ === Object.prototype

大例子

function Person(name){
  this.name = name
}
function Mother() {}
Mother.prototype = {      // Mother的原型
  age: 18,
  home: ['Beijing']
}
Person.prototype = new Mother()  // Person的原型为Mother

var p1 = new Person('Jack') // p1: 'Jack'; __proto__: 18, ['Beijing']
var p2 = new Person('Mark') // p2: 'Mark'; __proto__: 18, ['Beijing']
p1.age = 20 // p1: 'Jack', 20; __proto__: 18, ['Beijing']
/*
实例不能改变原型的基本值属性,正如你洗剪吹染黄毛跟你妈无关
在p1实例下增加一个age属性的普通操作,与原型无关
*/

p1.home[0] = 'Shenzhen' // p1: 'Jack', 20; __proto__: 18, ['Shenzhen']
// p2: 'Mark'; __proto__: 18, ['Shenzhen']
/*
此处动的是原型的home,所以p2也会跟着变动
*/

p1.home = ['Hangzhou', 'Guangzhou'] // p1: 'Jack', 20, ['Hangzhou', 'Guangzhou']; __proto__: 18, ['Shenzhen']
// p2: 'Mark'; __proto__: 18, ['Shenzhen']
/*
此处变动的是p1实例下增加一个home属性的普通操作,与原型无关
*/

/*
此处会有一个疑问,为什么p1.home[0] 改变的是原型的属性值,
而p1.home改变的是p1的属性值,请脱离代码到下方查看解释。
*/

Person.prototype.lastName = 'Jin'
/*
Person.prototypr是Mother,则相当于往Mother里加一个lastName属性,即Mother.lastName = 'Jin'
则p1: 'Jack', 20, ['Hangzhou', 'Guangzhou']; __proto__: 'Jin';  __proto__: 18, ['Shenzhen']
p2: 'Mark'; __proto__: 'Jin'; __proto__: 18, ['Shenzhen']

PS: 第一个__proto__ 指向的是Mother,第二个__proto__ 指向的是Mother。prototype
*/

Person.prototype = {
  age: 28,
  address: { country: 'USA', city: 'Washington' }
}
var p3 = new Person('Obama')
/*
重写Person原型,此时的Person的原型已经是一个新的对象了
但是p1、p2对应的原型还是原来的原型
这样理解,var a = 1; b = a; a = 2; c = a
则有 a = 2, b = 1, c = 2
所以:p1: 'Jack', 20, ['Hangzhou', 'Guangzhou']; __proto__:'Jin';  __proto__:18, ['Shenzhen']
p2: 'Mark'; __proto__: 'Jin'; __proto__: 18, ['Shenzhen']
p3: 'Obama'; __proto__: 28, {country: 'USA', city: 'Washington'}
*/

Mother.prototype.no = 9527
/*
改写原型的原型,动态反应到实例中。
注意,这里改写的是Mother.prototype,p1、p2会变,但上面p3跟Mother已经了无瓜葛了,不影响他。
p1: 'Jack', 20, ['Hangzhou', 'Guangzhou']; __proto__:'Jin';  __proto__:18, ['Shenzhen'], 9527
p2: 'Mark'; __proto__: 'Jin'; __proto__: 18, ['Shenzhen'], 9527
p3: 'Obama'; __proto__: 28, {country: 'USA', city: 'Washington'}
*/

Person.prototype = new Mother() //再次绑定
var p4 = new Person('Luffy');
/*
Person的原型再次绑定为Mother,所以与之前一样,p1、p2、p3均与原来一致,p4对应变化
p1: 'Jack', 20, ['Hangzhou', 'Guangzhou']; __proto__:'Jin';  __proto__:18, ['Shenzhen'], 9527
p2: 'Mark'; __proto__: 'Jin'; __proto__: 18, ['Shenzhen'], 9527
p3: 'Obama'; __proto__: 28, {country: 'USA', city: 'Washington'}
p4: 'Luffy'; __proto__: 'Jin'; __proto__: 18, ['Shenzhen'], 9527
*/

疑问:为什么p1.home[0] 改变的是原型的属性值,而p1.home改变的是p1的属性值?
跟对象赋值有点相像。
比如:

var obj = new Object()
obj.name = 'Wu'
obj.books = ['1','2']
console.log(obj) // obj {name: 'Wu', books: ['1', '2']}
var obj.name = 'Wu'
var obj.books = ['1','2']
// 直接报错

因为未定义obj,怎么给他赋值嘛。

同理,因为p1中未定义home,却想直接操作home[0],所以是无法直接改变home[0]的,因此就在p1中找不到home[0]。由于原型链中有一个搜索机制,在实例中找不到就往原型找,再找不到就往原型的原型找,一层层的找。
所以p1.home[0] 其实是 Mother.prototype.home[0],因为Mother.prototype中有定义home。
而p1.home是直接赋值,没有具体操作home的某个元素或者属性。所以可以直接在实例中赋值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值