JavaScript原型的个人理解

202 阅读9分钟

一、对象的创建方式

1.用new构造函数创建

  <script>
    // 通过new构造函数创建
    var obj = new Object()

    //添加属性和方法
    obj.name = '小艾同学'
    obj.age = 23
    obj.sayHi = function () {
      console.log('Hi~');
    }

    var obj2 = new Object()
    obj2.name = 'cp'
    obj2.age = 28
    obj2.sayHi = function () {
      console.log('Hi~');
    }
    console.log(obj);
    console.log(obj2);
    /*
    缺点:1.添加属性或方法麻烦
          2.一次只能创建一次,代码的复用性差
    */
  </script>

2.字面量方法创建对象

  <script>
    let obj = {
      name: '小艾同学',
      age: 23,
      say: function () {
        console.log('hi~');
      }
    }
    console.dir(obj);
    /*
    字面量创建对象:一次只能创建一次,代码的复用性差
    */
  </script>

3.通过工厂函数创建对象

  <script>
    // 工厂函数:利用函数将字面量对象封装,并返回这个对象。解决一次性对象复用性差的问题
    function Person(name, age) {
      let obj = {
        name: name,
        age: age,
        say: function () {
          console.log('hi~');
        }
      }
      return obj
    }

    let xa = Person('小艾同学', 23)
    let cp = Person('cp', 28)

    console.log(xa);
    console.log(cp);

    //缺点:1. 无法识别对象,返回的都是Object
  </script>

4.自定义构造函数

  <script>
    //自定义构造函数注意:1.首字母大写,2.需要配合new操作符
    function Person(name, age) {
      this.name = name
      this.age = age
      this.say = function () {
        console.log('hi~');
      }
    }

    let xa = new Person('小艾同学', 23)
    let cp = new Person('cp', 28)
    //此时解决了无法判断对象,类型的问题
    console.log(xa);
    console.log(cp);
    console.log(xa === cp);//false

    // 两个相同的方法,却在内存中占用了两个地址值
    console.log(xa.say() === cp.say());//true
    console.log(xa.say === cp.say);//false
  </script>

5.解决内存浪费问题

    // 方法一:将公共的方法抽离出来,确保该方法只占一份内存
    //缺点 : 将方法放在全局作用域,会污染全局
    function sayHi() {
      console.log('hi~');
    }
    function Person(name, age) {
      this.name = name
      this.age = age
      this.say = sayHi
    }
    let xa = new Person('小艾同学', 23)
    let cp = new Person('cp', 28)
    console.log(xa);
    console.log(cp);

    console.log(xa === cp);//false
    // 占用内存问题已经解决
    console.log(xa.say === cp.say);//true


    // 方法二:将公共的方法存入一个对象中
    //解决了所有问题,但是需要重新创建一个新对象,写法不够优雅
    var tools = {
      sayHi: function () {
        console.log('hi~');
      },
      sayBay: function () {
        console.log('Bay~');
      }
    }
    function Person(name, age) {
      this.name = name
      this.age = age
      this.say = tools.sayHi
      this.bay = tools.sayBay
    }
    let xa = new Person('小艾同学', 23)
    let cp = new Person('cp', 28)
    console.log(xa);
    console.log(cp);

    console.log(xa === cp);//false
    // 占用内存问题已经解决
    console.log(xa.say === cp.say);//true
    console.log(xa.bay === cp.bay);//true

二、原型对象prototype

1.任何函数都有prototypr属性 2.函数的prototype属性的值就是原型对象 3.通过构造函数new出来的对象称为实例对象,一个构造函数可以有多个实例对象 4.实例对象可以访问构造函数原型对象上的所有成员

  • 将一些公共的属性和方法挂载到原型对象上完成共享,省内存

    function Person(name, age) {
      this.name = name
      this.age = age
    }
    //将公共的方法提取到构造函数自带的原型对象中
    Person.prototype.say = function () {
      console.log('hi~');
    }

    let xa = new Person('小艾同学', 23)
    let cp = new Person('cp', 28)
    console.log(Person.prototype);

    // 实例对象能够访问构造函数原型对象上的任何成员
    xa.say()

    //同样解决了内存浪费的问题
    console.log(xa === cp);//false
    console.log(xa.say === cp.say);//true
原型对象的核心:内存复用,共享方法

内置对象的方法push(),max()等方法都是挂载在构造函数的原型上,避免内存浪费,且每个实例对象都能访问原型对象上的成员

1.new做了哪些事

  • 生成了一个空对象
  • 将生成的空对象的原型对象自动指向了构造函数的prototype
    • 伪代码 --> obj.proto === Person.prototype
  • 将构造函数的this指向这个空对象
  • 将属性和方法都挂载到这个对象上
  • 返回这个对象
    • 如果构造函数上没有写return,默认返回new创建的这个对象
    • 如果构造函数写了return
      • return的是一个基本数据类型,仍然返回new创建的对象
      • return的是一个对象,就会返回return后面的这个对象
	//伪代码模拟new操作符
	function newSelf(ctor, name, age) {
      // 1.创建一个空对象
      var obj = {}
      // 截取参数,为一个数组
      var res = Array.from(arguments).slice(1)
      // 2.改变this指向为这个空对象,并将数组参数传入
      ctor.apply(obj, res)
      // 3.将空对象的原型连接到构造函数的原型对象
      obj.__proto__ = ctor.prototype
      //4.返回这个对象
      return obj
    }

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

    var p1 = newSelf(Person, '小艾同学', 23)
    console.log(p1);

2.特殊的原型对象

  • Function.prototype区别于一般函数的prototype,不可修改,不可遍历, 并且是所有函数的原型对象
  • Function.prototype的值是一个函数,而普通函数的prototype是个含有constructor属性的普通对象
  • Object.prototype是所有对象原型链的尽头,Object.prototype.proto === null
    console.log(Function.prototype);//ƒ () { [native code] }
    console.log(Object.prototype.__proto__);//null
    function Person() { }
    console.log(Person.__proto__ === Function.prototype);

3._instanceof _

二、对象的原型__proto__

任何一个对象上都有个__proto__,指向构造函数的prototype。 是一个访问器属性, 暴露了通过它访问的对象的内部[[Prototype]] 。 核心功能 :便于我们查看当前对象的原型对象是谁

image.png

    //任何对象都有__proto__
    function Person(name) { this.name = name }
    Person.prototype.say = function () { console.log('hello') }
    let xa = new Person

    // 实例对象的__proto__指向了当前构造函数的prototype对象
    console.log(xa.__proto__);
    console.log(xa.__proto__ === Person.prototype);//true

    // 2.__proto__是一个私有属性,虽然__proto__指向原型对象,但不推荐用这种方式去操作原型对象, 浏览器为了方便开发者查看当前对象的原型对象是谁暴露出来的
    // 内部 使用 [[prototype]] 内置属性发生关联

三.constructor属性

constructor属性是每个原型对象上自带的属性 原型对象的constructor属性指向了构造函数 当直接给原型对象赋值一个对象时,需要用constructor做指针修正,将原型对象手动指回构造函数

    function Person() { }
    console.log(Person.prototype);
    console.log(Person.constructor);
    console.log(Person.prototype.constructor === Person);//true

    //应用场景
    function Star(name) {
      this.name = name
      this.bey = function () {
        console.log('bey~');
      }
    }
    //给原型对象赋值的是一个对象,原型对象就会被覆盖,此时的constructor就不再指向Star了,而是Object
    Star.prototype = {
      //需要在内部手动修改constructor指向
      constructor: Star,
      age: '18',
      say: function () {
        console.log('hello');
      }
    }

    let xa = new Star('小艾同学')
    console.log(xa.constructor);// constructor修改指向前:Object 修改指向后:Star

四.三者的关系

  • 构造函数 和 原型对象

    • 构造函数 通过 prototype属性来访问到原型对象
    • 原型对象 通过 constructor属性来访问到构造函数
  • 构造函数 和 实例对象的关系

    • 构造函数通过new创建出来实例对象
    • 实例对象不能去直接访问到构造函数
  • 原型对象 和 实例对象的关系

    • 实例对象通过__proto__属性访问原型对象上的任意成员
    • 实例对象通过原型对象的constructor属性可以间接的访问构造函数

    function Person() { }
    // 构造函数 和 原型对象
    // 1. 构造函数 通过 prototype属性来访问到原型对象
    console.log(Person.prototype);
    // 2. 原型对象 通过 constructor属性来访问到构造函数
    console.log(Person.prototype.constructor);//Person

    // 构造函数 和 实例对象的关系
    // 1. 构造函数通过new创建出来实例对象
    let xa = new Person()
    // 2. 实例对象不能去直接访问到构造函数

    // 原型对象 和 实例对象的关系
    // 1. 实例对象通过__proto__属性访问原型对象上的任意成员
    // 2. 实例对象通过原型对象的constructor属性可以间接的访问构造函数
    console.log(xa.__proto__ === Person.prototype);//true
    console.log(xa.constructor === Person);//true

五.原型链

对象有__proto__属性,指向了当前的原型对象,原型对象也是对象,也有__proto__属性,指向了原型对象的原型对象,当访问某个属性的时候,先在自身上找,没得找到就去__proto__指向的原型对象上查找,还没找到就去原型对象的原型对象上查找,这样一层一层向上查找会形成一个链式结构,就是原型链

		function Person() { }
    let p = new Person()
    console.log(p.__proto__ === Person.prototype);//true
    console.log(Person.prototype.__proto__ === Object.prototype);//true
    console.log(Object.prototype.__proto__);//null
    // p的原型链:p --> Person.prototype --> Object.prototype --> null

    // 内置对象的原型链
    // 1.数组
    //[]本质上是 [] = new Array(),空数组也是两个不同的地址值
    console.log([] == []);//false
    console.log([] === []);//false

    let arr = []
    console.log(arr.__proto__ === Array.prototype);//true
    console.log(Array.prototype.__proto__ === Object.prototype);//true
    console.log(Object.prototype.__proto__);//null
    //数组的原型链:arr --> Array.prototype --> Object.prototype --> null

    // 2.Date
    let date = new Date()
    console.log(date.__proto__ === Date.prototype);//true
    console.log(Date.prototype.__proto__ === Object.prototype);//true
    console.log(Object.prototype.__proto__);//null
    //Date的原型链: date --> Date.prototype --> Object.prototype --> null

    //3.Math
    //Math本质上就是一个对象,不能和new一起使用
    console.log(Math);
    console.log(Math.__proto__);//Object.prototype
    console.log(Object.prototype.__proto__);//null
    //Math的原型链:Math --> Object.prototype --> null

完整版原型链

    function Person() { }
    let p = new Person
    // 1.把构造函数当做函数
    // 构造函数:Person
    // 原型对象:Person.prototype
    // 实例对象:p
    console.log(Person.prototype);
    console.log(Person.prototype.constructor);//Person
    console.log(p.__proto__);

    // 2.把Person函数当对象来看
    //底层: let Person = new Function()
    // 构造函数:Function
    // 原型对象:Function.prototype
    // 实例对象:Person
    console.log(Person.__proto__);//ƒ () { [native code] }
    console.log(Person.__proto__ === Function.prototype);//true
    console.log(Function.prototype.constructor);//ƒ Function() { [native code] }
    console.log(Function.prototype.constructor === Function);//true

    // 3. 把Object当构造函数来看
    var obj = new Object();
    // 构造函数: Object
    // 原型对象: Object.prototype
    // 实例对象: obj
    console.log(obj.__proto__ === Object.prototype);//true

    // 4. 把Object当实例对象来看
    // 底层 var Object = new Function();
    // 构造函数: Function
    // 原型对象: Function.prototype
    // 实例对象: Object
    console.log(Object.__proto__);//ƒ () { [native code] }
    console.log(Object.__proto__ === Function.prototype);//true

    // 5. 把Function当做构造函数看
    // 底层 var Function = new Function();
    // 构造函数: Function
    // 原型对象: Function.prototype
    // 实例对象: Function
    console.log(Function.__proto__ === Function.prototype);//true

    let arr = new Array()
    console.log(Array.__proto__); //ƒ () { [native code] }
    console.log(Array.__proto__ === Function.prototype);//true

六.属性查找原则

查找一个属性时,如果在当前的对象上找不到该属性,就委托给它的原型对象继续查找,如果原型对象上依然没有找到,那就继续委托给原型对象的源性对象查找,直到找到Object.prototype时,如果有就返回该属性,如果没有就返回undefined;如果找到,不管在哪一层找到了都会直接返回,不在继续向下查找。_

    function Person(name, age) {
      this.name = name
      this.age = age
      this.say = function () {
        console.log('hello');
      }
    }
    Person.prototype.sex = '女'
    Object.prototype.money = 666

    let p = new Person('小艾同学', 23)
    console.log(p.name);//小艾同学
    console.log(p.sex);//女
    console.log(p.money);//666
    console.log(Person.prototype.name);//undefined
    console.log(Person.prototype.money);//666 

七.原型设置原则

不会影响其他原型对象

    /*
    属性设置原则:
      如果该对象上有该属性,就把原来的值不该掉,不会影响原型上的属性
      如果该对象没有属性,就会添加该属性,也不会影响原型上的属性
    */
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.name = 'cp'
    Object.prototype.money = 666

    let p = new Person('小艾同学', 23)
    p.money = 0

    console.log(p.name);//小艾同学
    console.log(p.money);//0
    //不会影响原型上的属性
    console.log(Person.prototype.money);//666