JS:原型-原型链-ES5继承

1. 普通对象的原型

1.什么是对象原型?
2.获取对象原型的方法?
3.对象原型的作用?

原型:JavaScript当中每个对象都有一个特殊的内置属性[[prototype]],这个特殊的对象可以指向另一个对象

  • 获取对象原型 两种方法:

1.不能这样获取obj.[[prototype]]
2.这样获取obj.__proto__

   使用的时候需要判断一下。

    这个是浏览器实现的。对象原型,也可以称为隐式原型。因为一般不常用。

    下文为了方便使用\_\_proto\_\_
  1. 标准的方法Object.getPrototypeOf(obj) 推荐使用标准的方法
  • 疑问:这个原型有什么用呢?

当我们通过[[get]]的方式获取一个属性对应的value时,

它会优先在自己的对象中查找,如果找到直接返回。

如果没有找到,那么会在原型对象中找。

2. 函数对象的原型

1.什么是函数的显式原型,和对象原型的区别?2.函数原型的作用?3.案例Student,将所有的函数定义放到了显式原型上。4.将函数看成是一个普通对象的时候,具备[[prototype]]属性。5.将函数看成是一个函数时,具备prototype属性。对象没有prototype,可以称为显式原型,一般直接用的。

是因为函数才有prototype,不是因为是对象。

对象的原型作用:

  • 查找key对应的value时,会找到原型身上。

函数原型的作用:

  • 用来构建对象时,给对象设置隐式原型的。再看new操作符

1.创建一个空对象2.将这个空对象赋值给this3.将函数的显式原型赋值给这个对象作为它的隐式原型。4.执行函数体中的代码5.将这个对象默认返回。function Foo() { // 1. 创建空的对象 // 2. 将Foo的prototype(显式)赋值给空的对象的__proto__(隐式) } console.log(Foo.prototype)var f1 = new Foo() var f2 = new Foo()console.log(f1.proto) console.log(f1.proto === Foo.prototype) // true console.log(f1.proto === f2.proto) // true 构造出来的对象的隐式原型都是同一个对象Foo.prototype。new第三步意味着什么?

     意味着我们通过Person构造函数创建出来的所有对象的\[\[prototype\]\]属性都指向Person.prototype

疑问:这个同一个对象有什么作用?

   当我们多个对象用于共同的值时,我们可以将它放到构造函数对象的显示原型上,

由构造函数创建出来的所有对象,都会共享这些属性。

function Studnet(name, age) {this.name = namethis.age = agethis.studying = function() {console.log(this.name + " is studying")}
}

var stu1 = new Student("Kobe", 18)
var stu2 = new Studnet("Curry", 18)

console.log(stu1.studying === stu2.studying) 
// false,重新创建了函数,我们不需要不同的函数。

// 因为隐式原型就是Student.prototype
Student.prototype.studying = function() {console.log(this.name + " is studying")
}

stu1.studying()
// 通过stu1隐式原型找到studying方法。this指向stu1(隐式绑定) 

3. 函数原型的属性

1.constructor2.操作属性3.结合内存图理解操作function Person(name, age) { this.name = name this.age = age }Person.prototype.running = function() { console.log(“running~”) }console.log(Person.prototype.constructor === Person) // truevar p1 = new Person(“Kobe”, 18) var p2 = new Person(“Curry”, 30)// 进行操作 console.log(p1.name) console.log(p2.name)p1.running() p2.running()// 新增属性 Person.prototype.address = “中国” p2.isAdmin = true // 添加p2的属性,原型对象上无isAdmin// 获取属性 console.log(p1.address)console.log(p1.isAdmin) // undefined console.log(p2.isAdmin)// 修改address p1.address = “广州” console.log(p2.address) // 中国总结:

1.构造函数上面有prototype属性,它指向构造函数的显式原型对象,显式原型对象中有constructor属性,指向了构造函数。
2.所有由new关键字创建的对象的[[prototype]]属性,指向构造函数的显式原型对象,在显式原型对象中每个添加的方法属性,指向对应的方法对象。

4. 重写原型对象

使用场景:如果我们需要在原型上添加过多的属性,通常重写原型对象。

Person.prototype.message = "Hello World"
Person.prototype.info = {name: "wang", age: 18}
Person.prototype.running = function() {console.log("running~")}

/// 让构造函数的显式原型不再指向原有的默认指向的原型对象,指向一个新的空对象
Person.prototype = {message: "Hello World",info: {name: "wang", age: 18},running: function() {console.log("running~")},constructor: Person
}

console.log(Object.keys(Person.prototype))
// 这个constructor是可以枚举的。直接添加不可枚举,如果需要一致
Object.defineProperty(Person.prototype, "constructor", {configurable: true,enumerable: false,value: Person,writable: true
}) 

如果不写constructor: Person,这个constructor属性,会指向Object构造函数。

constructor默认属性描述符:

5. 面向对象的三大特性

1.概念
2.继承的思想

JavaScript支持面向对象编程也支持函数式编程。

面向对象编程 -->编程方式(范式)

怎么去创建一个类,根据这个类创建对象,然后操作。

  • 面向对象三大特性* 封装:将属性和方法封装到一个类中,可以称之为封装的过程。* 继承:可以减少重复的代码量,也是多态的前提(纯面向对象中)。* 多态:不同的对象在执行时表现出不同的形态。1.将普通变量封装到一个对象 ,将一个对象封装到一个类。最终抽象为类。2.不同的类,具有相同的属性和方法,可以抽取到一个父类中。子类继承父类就可以实现父类的属性。3.多态在js中更多表现是一个“鸭子类型”function Student(name, age, score) { this.name = name this.age = age this.score = score } Student.prototype.running = function() {} Student.prototype.fighting = function() {}function Teacher(name, age, salary) { this.name = name this.age = age this.salary = salary } Teacher.prototype.running = function() {} Teacher.prototype.teaching = function() {}// 抽取父类要实现继承,es5中需要用到原型链。

6. 对象的原型链

1.定义
2.默认原型链,自定义原型链
3.查找顺序,结合图
4.补充

定义:

  • 从一个对象上获取属性,如果当前对象中没有获取到就会去它的原型上获取。

1.默认原型链2.自定义的原型链var obj = { name: “wang”, age: 18 } console.log(obj.message) obj.proto = { // message: “hello a” } obj.proto.proto = { // message: “hello b” } obj.proto.proto.proto = { message: “hello c” } console.log(obj.message) console.log(obj.proto.proto.proto.proto === Object.prototype) console.log(obj.proto.proto.proto.proto.proto === null)查找顺序

1.obj上面查找
2.obj.__proto__上面查找
3.obj.__proto__.__proto__==>null 上面查找 undefined

补充:

原型链的顶层:null

所有构造函数继承Object构造函数。即Object是所有类的一个父类

7. 原型链实现继承ES5继承

function Student(name, age, score) {this.score = score
}
Student.prototype.fighting = function() {}

function Person(name, age) {this.name = name this.age = age
}
Person.prototype.running = function() {console.log("running~")
}

var stu1 = new Student() 

// 需要stu1调用running,自身的原型上去找,没有,再去原型的原型上找,指向的是Person.prototype

// 如何让二者关联?

方式一:父类的原型直接赋值给子类的原型 (错误的做法)

缺点:父类和子类共享一个原型对象,修改一个,另外一个也被修改。

Student.prototype = Person.prototype

Student.prototype.studying = function() {console.log("studying~")
}
var p = new Person()
p.studying()// 可以调用 

方式二:创建一个父类的实例对象,用这个实例作为子类的原型对象。

var p1 = new Person("wang", 18)
Student.prototype = p1 

解决了方式一缺点,

缺点:

继承了实例对象的属性。

如果删除子类中的赋值属性的代码,也会打印出属性,

但是打印的是继承父类的属性。

原型链继承的弊端:

1.某些属性其实是保存在p1对象上的;
2.直接打印对象看不到这个属性。
3.这个属性会被多个对象共享,如果这个对象是一个引用类型,那么就会造成问题。
4.不能给Person传递参数(让每个stu有自己的属性),因为这个对象是一次性创建的(没办法定制化)。

8. 借用构造函数实现-属性继承

  • 为了解决原型链继承中存在的问题,开发人员提供了一种新的技术:constructor stealing(借用构造函数)

做法:在子类的构造函数内部调用父类的构造函数

function Student(name, age, score) {Person.call(this, name, age) // 借用构造函数,调用时是Student对象this.score = score
}
Student.prototype.fighting = function() {}

function Person(name, age) {this.name = name this.age = age
}
Person.prototype.running = function() {console.log("running~")
}

var p1 = new Person("wang", 18)
Student.prototype = p1

var stu1 = new Student("Kobe", 18, 100)
console.log(stu1.age)
console.log(stu1.name)
console.log(stu1) 

加上原型链继承就是组合借用继承。

缺点:

组合继承最大的问题就父类的构造函数至少会被调用两次。

事实上,所有的子类实例事实上会拥有两份的父类属性。一个是本身,一个是原型上。

9. 最终继承方案-寄生组合式继承

9.1 原型式继承

  • 原型式继承的渊源:* 这种模式需要从 Douglas Crockford( 著名的前端大师,JSON的创立者 )在2006年写的一篇文章说起: Prototypal Inheritance in JavaScript* 它介绍了一种继承方法,而且这种继承方法不是通过构造函数来实现的* 最终的目的student对象的原型指向了person对象

9.2 寄生式继承

  • 寄生式(Parasitic)继承* 寄生式继承是与原型式继承紧密相关的一种思想,并且同样由道格拉斯·克罗克福德(Douglas Crockford)提出和推广的;* 寄生式继承的思路是结合原型类继承和工厂模式的一种方式;* 即创建一个封装继承过程的函数, 该函数在内部以某种方式来增强对象,最后再将这个对象返回;需要创建一个类似于p1的对象满足

1.必须创建出来一个对象2.将这个对象的隐式原型属性指向父类的显式原型属性3.将这个对象赋值给子类的显式原型function Person() {} function Student() {} // 之前的做法 // var p = new Person() // Studnet.prototype = p // 方案一 // var obj = {} // Object.setPrototypeOf(obj, Person.prototype) // Student.prototuype = obj// 方案二 // function F() {} // F.prototype = Person.prototype // Student.Prototype = new F()// 方案三 // var obj = Object.create(Person.prototype) // console.log(obj.proto === Person.prototype) // Student.prototype = obj// 封装工具函数 function inherit(Subtype, Supertype) { Subtype.prototype = Object.create(Supertype.prototype) Object.defineProperty(Subtype.prototype, “constructor”, { enumerable: false, configurable: true, wriable: true, value: Subtype }) }inherit(Student, Person)// 考虑兼容性问题 Object.create function createObject(o) { function F() {} F.prototype = o return new F() }将Subtype和Supertype联系在一起,称为寄生式函数。

整个过程称为寄生组合式继承。

9.3 寄生组合式继承

寄生组合式继承:

1.原型链2.借用构造函数3.原型式(对象之间)4.寄生式函数function inherit(Subtype, Supertype) { Subtype.prototype = Object.create(Supertype.prototype) Object.defineProperty(Subtype.prototype, “constructor”, { enumerable: false, configurable: true, wriable: true, value: Subtype }) } function Person(name, age) { this.name = name this.age = age } Person.prototype.running = function() { console.log(“running~”) }function Student(name, age, score) { Person.call(this, name, age) this.score = score }inherit(Student, Person) Student.prototype.studying = function() { console.log(“studying~”) }var stu1 = new Student(“Kobe”, 30, 10000000) console.log(stu1) stu1.studying() stu1.running()

完美解决

11. 原型-寄生思想的来源

Douglas Crockford研究对象之间的继承。实际上实现继承并不好。

var obj = {name: "wang",age: 18
}

// 原型式继承
var info = {}
info.__proto__ = obj
// 如果创建多个继承的对象,出现大量重复代码

// 使用原型式继承__proto__会出现浏览器兼容性问题,因此没有用
function createObject(o) {function F() {}F.prototype = oreturn new F()
}

// var info1 = createObject(obj)
// info1.height = 188
// var info2 = createObject(obj)
// info2.height = 199
// 目前的代码有弊端,没有自己特有的属性,需要单独添加。
// 寄生式函数
function createInfo(o, name, age, height) {var newObj = createObject(o)newObj.name = namenewObj.age = agenewObj.height = heightreturn newObj
}

// 创建一系列对象
var info1 = createInfo(obj, "Kobe", 18, 222)
console.log(info1) 

10. 原型继承关系

内存图:

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值