• 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道面试题,并对每个问题作出了回答和解析。

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

    部分文档展示:



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

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

  • 相关阅读:
    [Machine Learning][Part 8]神经网络的学习训练过程
    如何让FileBeat支持http的output插件
    什么是向上管理
    NLP学习(1)-搭建环境
    python批量将多年降水的nc数据处理为季节性平均降水量或年降水量
    【arm实验2】按键中断事件控制实验
    借助 Docker 来搭 Nginx 的积木:快速实现高性能二维码服务
    java计算机毕业设计基于springboo+vue的学生毕业离校系统
    C语言进阶文件操作
    Redis的事务和锁机制(乐观锁和悲观锁)
  • 原文地址:https://blog.csdn.net/web2022050901/article/details/127069905