zoukankan      html  css  js  c++  java
  • 557 原型prototype和原型链__proto__:原理,函数的三种角色,for in,手写new

    向对象底层运行机制的三句话

    * 面向对象底层运行机制的三句话:

    * 1.每一个函数(自定义类、内置类、普通函数)都具备一个属性: prototype[原型],属性值是一个对象[浏览器内置开辟的堆],在这个对象中存储的是,当前类供应给实例调用的公共属性方法。【prototype是对象,不是函数】

    * 2.在prototype这个对象中,内置一个constructor属性[类的构造函数],属性值是当前类本身。

    * 3.每一个对象(普通对象、数组对象、实例、prototype也是对象...)都具备一个属性:__proto__[原型链],属性值是当前对象(实例)所属类的prototype。

    function Func() {
      this.name = 'xxx';
      this.age = 20;
      this.say = function say() {
        console.log(`my name is ${this.name},i'm ${this.age} years old!`);
      };
    }
    Func.prototype.say = function say() {
      console.log('say prototype');
    };
    Func.prototype.eat = function eat() {
      console.log('eat prototype');
    };
    var f1 = new Func;
    var f2 = new Func;
    

    原型和原型链练习题1

    function Fn() {
      this.x = 100;
      this.y = 200;
      this.getX = function () {
        console.log(this.x);
      }
    }
    Fn.prototype.getX = function () {
      console.log(this.x);
    };
    Fn.prototype.getY = function () {
      console.log(this.y);
    };
    let f1 = new Fn;
    let f2 = new Fn;
    console.log(f1.getX === f2.getX); // false
    console.log(f1.getY === f2.getY); // true
    console.log(f1.__proto__.getY === Fn.prototype.getY); // true
    console.log(f1.__proto__.getX === f2.getX); // false
    console.log(f1.getX === Fn.prototype.getX); // false
    console.log(f1.constructor); // Fn
    console.log(Fn.prototype.__proto__.constructor); // Object
    f1.getX(); // 100
    f1.__proto__.getX(); // undefined
    f2.getY(); // 200
    Fn.prototype.getY(); // undefined
    

    原型和原型链练习题2

    function fun() {
      this.a = 0;
      this.b = function () {
        alert(this.a);
      }
    }
    
    fun.prototype = {
      b: function () {
        this.a = 20;
        alert(this.a);
      },
      c: function () {
        this.a = 30;
        alert(this.a)
      }
    }
    
    var my_fun = new fun();
    my_fun.b(); // 字符串0
    my_fun.c(); // 字符串30
    console.log(my_fun.a) // 30
    

    深入原型,体验函数的三种角色

    我的总结:

    1、Function是Function自己的实例对象,所以Function的__proto__指向Function.prototype。

    2、Object是Function的实例对象,所以Object的__proto__指向Function.prototype。

    3、Function.prototype是一个对象,所以Function.prototype的__proto__指向Object.prototype。

    4、所有函数都是Function的实例,所以函数的__proto__指向Function.prototype。

    5、Function.prototype是一个匿名空函数。


    基于内置类的原型扩展方法,解决for in弊端

    向内置类的原型上扩展方法,存在的细节知识:

    • 1.为了防止自己设定的方法覆盖内置的方法,我们设置的方法名加前缀
    • 2.优势:使用起来方便,和内置方法类似,直接让实例调用即可
    • 3.方法中的this一般是当前要操作的实例(也就不需要基于形参传递实例进来了)
    • 4.优势:只要保证方法的返回结果还是当前类的实例,那么我们就可以基于“链式方法”调用当前类中提供的其它方法【返回结果是谁的实例,就可以继续调用谁的方法】

    for of :用于有 Symbol.iterator 的数据结构

    数组、类数组、new Set、map、字符串,对象不具备Symbol.iterator

    Array.prototype.myDistinct = function myDistinct() {
      // this -> arr
      // ES6中的Set结构(不重复的数组):Set类的实例
      // let newArr = [...new Set(this)]; // 方法1
      let newArr = Array.from(new Set(this)); // 方法2
      return newArr;
    };
    let arr = [1, 2, 3, 2, 3, 2, 1, 2, 3, 4, 3, 2, 1];
    arr = arr.myDistinct().reverse().map(item => item * 10);
    console.log(arr);
    
    
    // ---------------
    
    
    /* 
    FOR IN 遍历对象【的弊端】:
    所有可以被枚举的属性都可以遍历到(大部分私有属性和自己向内置类原型上扩展的属性,私有属性length不能遍历)。
    解决方案:
    (1)处理 for in 循环的时候,我们需要加hasOwnProperty判断;
    (2)使用Object.keys。
    */
    Object.prototype.myXx = function () { };
    let obj = {
      name: 'xxx',
      age: 20
    };
    
    for (let key in obj) {
      if (!obj.hasOwnProperty(key)) break;
      console.log(key, obj[key]);
    }
    
    // 我的写法
    for (let k in obj) {
      if (obj.hasOwnProperty(k)) console.log(k, obj[k])
    }
    
    // 这样也可以,Object.keys(obj)只会获取所有私有的属性
    Object.keys(obj).forEach(key => console.log(key, obj[key]));
    

    手写new和Object.create

    • Func:即将创建的类
    • args:给当前这个类执行时候传递的实参
      1. 拥有普通函数执行的一面,让他作为普通函数执行
      2. 创建一个实例对象
        -> 空对象
        -> 对象.proto===类.prototype
      3. 方法执行的时候,方法中的this是实例对象
      4. 判断方法的返回结果,如果返回的不是引用类型值,默认把实例返回

    Object.create(xxx):创建一个空对象,并且让把xxx作为创建对象的原型(空对象.__proto __ = xxx),xxx必须是对象或者null,如果xxx是null,则创建一个没有任何原型指向的空对象

    我的版本
    1. 在构造函数代码开始执行之前,创建一个空对象;
    2. 设置新对象的__proto__指向构造函数的prototype对象,p.proto = XXXX.prototype;
    3. 执行构造函数,修改this的指向,让构造函数中的this指向创建出来的空对象,拷贝构造函数中的方法、属性给新对象;
    4. 判断构造函数返回的结果,如果返回的不是引用类型值,默认把实例返回;如果返回的是引用类型值,则返回引用类型。
    // 有些简单函数就不用再画开辟堆保存函数的图
    // Object.create处理兼容
    Object.create = function create(prototype) {
      function Fn() { }; // 【创建一个空对象】
      Fn.prototype = prototype; // 【让空对象的prototype指向prototype】
      Fn.prototype.constructor = Fn
      return new Fn; // 【返回一个实例对象】
    
      // 也可以下面这样写,但是不推荐用__proto__
      // Fn.__proto__ = prototype
      // return Fn
    };
    
    function _new(Func, ...args) {
      // 【把Func.prototype作为新对象obj的原型,即obj.__proto__ = Func.prototype】
      let obj = Object.create(Func.prototype);
      // 【执行Func,让Func中的this指向obj。】
      let result = Func.call(obj, ...args);
      // 【不是引用类型值,返回obj。undefined == null。】
      if (result == null || !/^(object|function)$/.test(typeof result)) return obj;
      return result;
    }
    
    
    function Dog(name) {
      this.name = name;
    }
    Dog.prototype.bark = function () {
      console.log('wangwang');
    };
    Dog.prototype.sayName = function () {
      console.log('my name is ' + this.name);
    };
    
    let sanmao = _new(Dog, '三毛');
    sanmao.bark(); //=>"wangwang"
    sanmao.sayName(); //=>"my name is 三毛"
    console.log(sanmao instanceof Dog); //=>true
    

    练习题1

    (function (prototype) {
      function validateNum(num) {
        num = Number(num);
        // 不能用 typeof num == 'NaN'判断,因为 typeof NaN 是"number"。可以用 Number.isNaN(num)
        return isNaN(num) ? 0 : num;
      }
      prototype.plus = function plus(num) {
        // this  =>  对象数据类型的值
        num = validateNum(num);
        return this + num; // 直接 return this + validateNum(num) ,一行代码搞定
      };
      prototype.minus = function minus(num) {
        num = validateNum(num);
        return this - num;
      };
    })(Number.prototype);
    
    // 百度面试题
    // 完成如下的需求
    let n = 10;
    let m = n.plus(10).minus(5);
    console.log(m); // => 15(10+10-5) 
    

    练习题2

    == 比较的时候,如果两边类型不一样:

    null == undefined,和其它值都不会相等

    对象 == 字符串:对象转换为字符差

    【其他】都转换为数字

    a等于什么值会让下面条件成立
    var a = ?;
    if (a == 1 && a == 2 && a == 3) {
        console.log('OK');
    }
    
    // 方法1:valueOf,也可以把valueOf改为toString
    var a = {
      n: 0,
      valueOf() {
        return ++this.n;
      }
    };
    if (a == 1 && a == 2 && a == 3) {
      console.log('OK');
    }
    
    
    // ------------------------
    
    
    // 方法2:toString、shift
    var a = [1, 2, 3];
    a.toString = a.shift;
    if (a == 1 && a == 2 && a == 3) {
      console.log('OK');
    }
    
    
    // ------------------------
    
    
    // 方法3:利用数据劫持完成,Object.defineProperty / Proxy 
    let n = 0;
    // 可以把 window 改为 globalThis ,这样不是在浏览器环境里也可以运行
    Object.defineProperty(window, 'a', {
      get() {
        return ++n;
      }
    });
    if (a == 1 && a == 2 && a == 3) {
      console.log('OK');
    } 
    

    练习题3

    下面代码输出的结果
    let obj = {
        2: 3,
        3: 4,
        length: 2,
        push: Array.prototype.push
    }
    obj.push(1);
    obj.push(2);
    console.log(obj);
    
    /* Array.prototype.push = function push(item) {
      // this  =>  arr(实例)
      // 1. 向实例的末尾追加一个新值
      // this[this.length]=item;
      // 2. 需要把实例的length累加1
      // this.length++;
      // 3. 返回新增后实例的length
    };
    let arr = [10, 20];
    arr.push(30); */
    
    let obj = {
      2: 3,
      3: 4,
      length: 2,
      push: Array.prototype.push
    }
    obj.push(1);
    // => this:obj  
    // obj[obj.length]=1   obj[2]=1
    // obj.length++  
    // => {2:1,3:4,length:3...}
    
    obj.push(2);
    // => this:obj
    // obj[obj.length]=2   obj[3]=2
    // obj.length++  
    // => {2:1,3:2,length:4...}
    
    console.log(obj); // {2: 1, 3: 2, length: 4, push: ƒ}
    

    练习题4

    function C1(name) {
      // 没有设置私有属性
      if (name) {
        this.name = name;
      }
    }
    
    function C2(name) {
      this.name = name;
      // this.name=undefined;
    }
    
    function C3(name) {
      this.name = name || 'join';
      // this.name='join';
    }
    
    C1.prototype.name = 'Tom';
    C2.prototype.name = 'Tom';
    C3.prototype.name = 'Tom';
    
    // (1)都没传参,Tomundefinedjoin;(2)new C2()、成员访问的优先级都是19,new Fun的是18
    alert((new C1().name) + (new C2().name) + (new C3().name)); 
    

    练习题5

    /*
     * 函数的三种角色
     *    1.普通函数(上下文/作用域/形参赋值/作用域链)
     *    2.构造函数(实例/原型/原型链) 
     *    3.普通对象(属性名和属性值)
     * 三种角色没有必然的联系
     */
    function Foo() {
      getName = function () {
        console.log(1);
      };
      return this;
    }
    Foo.getName = function () {
      console.log(2);
    };
    Foo.prototype.getName = function () {
      console.log(3);
    };
    var getName = function () {
      console.log(4);
    };
    function getName() {
      console.log(5);
    }
    Foo.getName();
    getName();
    // 普通函数,执行的返回结果,再调用getName
    Foo().getName();
    getName();
    // 【先成员访问,得到函数Foo.getName,此时没执行;然后new,new会执行函数。不是先得到函数Foo.getName,然后执行Foo.getName,再然后new。】
    new Foo.getName();
    new Foo().getName();
    // (1)new Foo():实例;(2)new Foo().getName:就是函数function () {console.log(3)}; (3)new 函数():执行这个函数,输出3,返回实例
    new new Foo().getName();
    

    1594476321466

    1594476338366
    1594476359760

    练习题6

    // 有些简单函数就不用再画开辟堆保存函数的图
    function Fn() {
        let a = 1;
        this.a = a;
    }
    Fn.prototype.say = function () {
        this.a = 2;
    }
    Fn.prototype = new Fn;
    let f1 = new Fn;
    
    Fn.prototype.b = function () {
        this.a = 3;
    };
    console.log(f1.a);
    console.log(f1.prototype);
    console.log(f1.b);
    console.log(f1.hasOwnProperty('b'));
    console.log('b' in f1);
    console.log(f1.constructor == Fn);
    

  • 相关阅读:
    阿里云主机如何重装系统?
    Linux下mysql新建账号及权限设置
    Ahead-of-time compilation(AOT)
    XBOX ONE游戏开发常见问题
    云计算之路-阿里云上:RDS用户的烦恼
    数字证书原理
    《你不常用的c#之二》:略谈GCHandle
    批处理文件指定jre路径启动java桌面应用程序
    关于OAUTH2.0的极品好文
    第一天接触Orchard
  • 原文地址:https://www.cnblogs.com/jianjie/p/13862940.html
Copyright © 2011-2022 走看看