面向对象编程-原型-原型链-继承-手动实现class

92 阅读7分钟

额外小知识

  1. with语句:可以形成自己的作用域 它会先在with的参数中寻找变量 之后再往上寻找
with(obj){
  console.log(obj)
}
  1. eval函数 eval可以传入字符串参数 这个可以执行字符串代码 一般情况都在webpack配置中 设置devtool='eval' 它的作用是把字符串代码 编程了es5代码
var jsString='var name='11''
eval(jsString)

面向对象编程

定义:

每个对象都应该有属于自己的对象

属性描述符分类:(默认都是false) 所以都不会有默认的

  1. 数据属性描述符
  • configurable:Boolean 是否可以通过delect删除 是否可以修改它的特性
  • enumerable:Boolean 是否可以通过for-in或者Object.keys()返回该属性 是否可以看到或者枚举到
  • writable:Boolean 是否可以修改属性
  • value : 属性值 2.存取属性描述符
  • configurable: Boolean 是否可以通过delect删除 是否可以修改的它特性 是否可以修改存取属性描述符
  • enumerable: boolean 是否可以通过for-in或者Object.keys()返回该属性 是否可以查看到 枚举到
  • get:function(){ // 在进行一些操作逻辑 return this.name },
  • set:function(value){ // 在进行一些操作逻辑 return this.name=value } 3.其他的api 对象的get set方法
var obj={
   name:111,
   set name(value){
     return this.name=value
   }
   get name(){
      return this.name
   }
}
获取对象上某一属性的属性描述符
object.getOwnpropertyDescriptot(obj,'name')

获取对象上全部属性描述符
object.getOwnpropertyDescriptots(obj)

禁止对象继续添加新的属性
object.preventExtensions(obj)

禁止对象配置/删除里面的属性
object.seal(obj)

禁止对象属性不可修改
object.freeze(obj)

object.defineProperty('哪一个对象','哪一个属性/如果没有这个属性就会自动添加',{这个属性的描述配置(object)})

object.defineProperties(obj,{
    name:{
     configurable : true,
     enumerable: false
    },
     age:{
     configurable : true,
     enumerable: false
    }
})

构造函数(构造器)

定义

通常是我们在创建对象时执行的函数 通过new调用 首先会在内存创建一个新的对象,他内部的proto会被赋值为该构造函数的prototype属性 然后把this赋予给这个对象 执行函数内部代码 自动返回那个对象(this)

let obj={}
function foo (){
  obj=this
  this._ _proto_ _=foo.prototype
  return obj
}

let p=new foo()

原型[proto]

早期ECMA没有提供原型 都是浏览器提供的_ proto _ 方便查看 ES5之后提供了object.getPrototypeOf(ob) 来查看这个对象的原型

用处

当我们从一个对象获取某一属性 他会获取该对象的get操作 在当前对象中查找get操作 如果没有找到 会找他的原型链去寻找get方法 可以在他的原型添加 **obj._ proto _.age=18 在原型里面添加 **是为了以后的实现继承

原型[prototype]这是一个属性

在new中把这个函数的原型给了这个函数的prototype但是因为foo.prototype中的enymerable(可枚举 查看属性)是false 所以看不见 所有函数原型里有个constructor属性 这个属性其实就是函数本身 也可以通过foo.prototype.constructor.name 拿到函数的名称

foo.prototype.name=11
foo.prototype.age=877
也可以
foo.prototype={
    name:11,
    age:877
}
他就把foo指向新的prototype对象 因为没有constructor 他的枚举还是false 所以要用Object.defindPropertye(foo.prototype,'constructor',
  {
     configurable: true 是否可以通过delect删除 是否可以修改的它特性 是否可以修改存取属性描述符 
    enumerable: false 是否可以通过for-in或者Object.keys()返回该属性
    writable: true 是否可以修改属性   
    value:foo   属性值 
  }
)
可以把共性的东西放在原型里面
function Person(name, age, height, address) {
  this.name = name
  this.age = age
  this.height = height
  this.address = address
}

Person.prototype.eating = function() {
  console.log(this.name + "在吃东西~")
}

Person.prototype.running = function() {
  console.log(this.name + "在跑步~")
}

var p1 = new Person("why", 18, 1.88, "北京市")
var p2 = new Person("kobe", 20, 1.98, "洛杉矶市")

p1.eating()
p2.eating()

面向对象的特征

封装

我们将属性和方法封装在一个函数里面(编写函数的过程)

继承

重复利用一些代码(复用) 继承时多肽的前提(利用原型链实现继承)

多态

不同的对象在执行时表现的不同的形态

继承 1.使用原型链实现继承

function Person(name) {
  this.name = "why"
  this.friends = []
}
Person.prototype.eating = function() {
  console.log(this.name + " eating~")
}
// 子类: 特有属性和方法   这个this就是student对象
function Student(name,age) {
    // 解决弊端的方案
  Person.call(this,name,age)
  this.sno = 111
}
Student.prototype =  new Person()
// 这种可以运行 但是从面向对象角度来说不正确  这样就可以其他人分享student添加原型的东西了
// Student.prototype =  Person.prototype
Student.prototype.studying = function() {
  console.log(this.name + " studying~")
}
  var stu = new Student("why", 18, ["kobe"], 111, 100)
  console.log(stu)
  stu.eating()

这样做有三个弊端

  • 继承的属性看不到 因为时放在原型里面了
  • 获取引用 修改引用原型的值会互相影响 因为他是通过get操作寻找到原型链直接找到了这个方法 stu.friend.push('kobe') 这样改变了原型链上的方法 但是直接修改本对象的值 是新增到自己的空间里 stu.name='xsh' stu.friend=[]
  • 不好传参数 改造 借用构造函数 在子类引用父类函数 Person.call(this,name,age) 这种方法也是有弊端的
  1. Person 被调用了多次 并且创建一次调用一次
  2. stu原型对象回多出一些属性 但是这些属性没必要存在的 因为stu里面已经有了

通过对象的原型链继承 来实现函数的寄生式继承

第一种 使用 Object.setPrototypeOf(新对象,需要继承的对象)
function foo (obj){
    var newObject={}
    Object.setPrototypeOf(newObject,obj)
}
第二种 使用new函数 赋值创建关系
function foo1(obj){
    function fn(){}
    fn.prototype=obj
    var result = new fn()
    return result
}

第三种直接使用Object.create
var newObject = Object.create(obj)

寄生式继承

定义 原生式继承加上工厂函数

var personObj = {
  running: function() {
    console.log("running")
  }
}

// 工厂函数
function createStudent(name) {
    // 运用了第三种方式
  var stu = Object.create(personObj)
  stu.name = name
  stu.studying = function() {
    console.log("studying~")
  }
  return stu
}

var stuObj = createStudent("why")
var stuObj1 = createStudent("kobe")
var stuObj2 = createStudent("james")

最终版本 寄生组合式继承

// 是为了创建新的对象 来继承父类的原型
function createObject(o) {
    function Fn() {}
    Fn.prototype = o
    return new Fn()
  }
  // 是为了把父与子原型建立关系
  function inheritPrototype(SubType 子类, SuperType 父类) {
    SubType.prototype = Objec.createObject(SuperType.prototype)
    // 也可以直接用Object的create
    // SubType.prototype = Objec.create(SuperType.prototype)
    
    // 因为直接继承创建的是一个对象 没有constructor这个属性 这个属性对应的是这个调用的方法本身this 所以在打印的时候不会显示name
    Object.defineProperty(SubType.prototype, "constructor", {
      enumerable: false,
      configurable: true,
      writable: true,
      value: SubType
    })
  }
  // 夫类
  function Person(name, age, friends) {
    this.name = name
    this.age = age
    this.friends = friends
  }
  
  Person.prototype.running = function() {
    console.log("running~")
  }
  
  Person.prototype.eating = function() {
    console.log("eating~")
  }
  
  
  function Student(name, age, friends, sno, score) {
    Person.call(this, name, age, friends)
    this.sno = sno
    this.score = score
  }
  // 建立父与子原型关系
  inheritPrototype(Student, Person)
  
  Student.prototype.studying = function() {
    console.log("studying~")
  }
  
  var stu = new Student("why", 18, ["kobe"], 111, 100)
  console.log(stu)
  stu.studying()
  stu.running()
  stu.eating()
  
  console.log(stu.constructor.name)

额外扩展
  // Objec.create(obj,{
        // 这个属性是添加到返回对象的本身上面 而不是原型上面
        age:{
          // 添加的是属性描述符
          configurable: Boolean 是否可以通过delect删除 是否可以修改的它特性 是否可以修改存取属性描述符 
          enumerable: boolean 是否可以通过for-in或者Object.keys()返回该属性
          writable: boolean 是否可以修改属性   
          value:111   属性值 
        }
      })

    // 判断是否为自己的还是原型的
    //   obj.hasOwnProprtype(name) 
    // in 操作符 (不管是在原型还是本身的)== for(var key in obj){}
    // 'name' in obj
    // instanceof 检测构造函数的pototype 是否出现在对象的原型链上 inf必须是个函数
    // stu instanceof inf   检查stu的原型是否在inf的原型链上
    // isPrototypeOf 某一个对象是否出现在原型链上 后面是必须对象
    // console.log(Person.prototype.isPrototypeOf(p))

函数的proto prototype与对象的proto prototype有啥区别

  var obj = {
  name: "why"
}

console.log(obj.__proto__)

// 对象里面是有一个__proto__对象: 隐式原型对象

// Foo是一个函数, 那么它会有一个显示原型对象: Foo.prototype
// Foo.prototype来自哪里?
// 答案: 创建了一个函数, Foo.prototype = { constructor: Foo }

// Foo是一个对象, 那么它会有一个隐式原型对象: Foo.__proto__
// Foo.__proto__来自哪里?
// 答案: new Function()  Foo.__proto__ = Function.prototype
// Function.prototype = { constructor: Function }

// var Foo = new Function()
function Foo() {

}

console.log(Foo.prototype === Foo.__proto__)
console.log(Foo.prototype.constructor)
console.log(Foo.__proto__.constructor)


var foo1 = new Foo()
var obj1 = new Object()

console.log(Object.getOwnPropertyDescriptors(Function.__proto__))