初级前端程序媛对原型的部分重新解读

1,960 阅读4分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战

  • 本文章以小知识点的形式对于原型进行解读(有争议以及表达不清楚请大家帮忙指正~)

原型初体验


// 简单定义一个数组

let arr = ["jxx"];
console.info(arr.concat('hhh'));
// 根据这小例子 在控制台的输出一下 
// 我们可以看到一个数组类型[[Prototype]]的属性,点开这个属性里面会有很多数组方法
// 其中还包括一个对象类型[[Prototype]]的属性
// 继续点开对象类型[[Prototype]]的属性,其中会有很多对象相关的方法,其中包括__proto__的数组方法,点开还会发现对象类型[[Prototype]],
// 接着点开还会发现括__proto__的对象方法,再接着点开还会发现一个值为null的__proto__的属性,这就是原型链的尽头(Object原型的原型是null)

从上述代码块中可以发现两个属性,分别是prototype和__proto__。

简单的用大白话来解释一下

  • prototype:用来定义构造函数的原型(意思就是通过这个构造函创建的所有对象都可以使用这个原型里面的属性),一般是服务于函数实例化;

  • __ proto __ : 并不是严格意义的属性,是一个getter和setter方法,一般是是服务于函数对象, 其中 当__ proto __有值时可以对指定对象的原型进行设置没有值时则可以获取,不过这是不标准的。标准的获取和设置则是setPrototypeOf和getPrototypeOf方法。

函数可以有好几个父级


function() Person {};
let person1 = new Person();
    person2 = new Person();

PROTOTYPE.jpg

可以理解函数有两个原型,分别是__proto__和 prototype, __proto__可以认为是函数自己的原型,prototype 是这个函数作为构造函数时实例化函数的原型, 所以说可以理解为__proto__是等于函数的 prototype,上图中我们根据一个构造函数来开辟函数空间,所以来说父级就都是相等的。

用原型来体现系统的构造函数


    let arr = {};   // Object
    hd.__proto__ === Object.prototype // ture

    let arr = []; // new Array
    arr.__proto__ === Array.prototype // true

    let str = ''; // new String
    str.__proto__ === String.prototype // true
    ...
  // 其他方法以此类推

自此个人理解,例如给构造函数Array的prototype上添加show方法,那么可以通过arr.show()来进行调用,

如果再次定义其他数组方法,其他任何数组方法也都是可以直接调用这个show()方法的

对原型进行自定义对象的设置


  • Object可以通过setPrototype()方法向实例的私有特性[[Prototype]]写入一个新值,这样就可以重写一个原型链之间的关系。
    let hd = {name: 'jxx'};
    let hh = {
        name: 'xiao',
        show() {
            console.info(11);
        }
    };
    hd.__proto__ === Object.protptype // true
    Object.setPrototypeOf(hd,hh);
    hd.show();

警告 Object.setPrototypeOf() 肯能会严重影响代码性能;

可能会涉及所有访问的那些修改的[[protptype]]的对象的代码;

为了避免使用这个方法造成性能下降可以通过Object.create()来创建一个对象,同时为其指定原型。

原型中对于constructor的引用


  • 就是可以通过原型找到我们的构造函数
function User(){};
// 通过constructor找到我们的函数
User.prototype.constructor === User; // true
// 也可以创建一个函数
let lisi = new User.prototype.constructor('里斯');
  • 那么其实我们可以通过construtor来向原型中追加方法
User.prototype = {
    constructor: User,
    show() {
        console.info(11);
    },
    render() {
        console.info(22);
    }
}
  • 一个玩法
 function User(name) {
    this.name = name;
    this.show = function() {
        console.info(this.name);
    };
}
let hd = new User('jxx');
function createByObject(obj, ...args) {
    const constructor = Object.getPrototypeOf(obj).constructor;
    return new constructor(...args);
}
let xx = createByObject(hd, 'jx');
xx.show();

原型检测


  • instanceof: 某一个对象的原型链上是否有另一个构造函数的prototype进行检测
  • isPrototypeOf(): 检测一个对象是否在另一个对象的原型链上
  • in与hasOwnProperty()的属性检测
let a = { url: 'jxx'};
let b = { name: 'hh'};
console.info('url' in a); // true
Object.prototype.web = 'xixi';
console.info('web' in a); // true
// 所以in不止检测对象,还会检测原型链 

// 当某一时刻只想要操作当前的对象则使用hasOwnProperty()
// console.info(a.hasOwnProperty('url')); // true
// console.info(a.hasOwnProperty('name')); // false

// 小知识点
// 当使用for...in...遍历对象但又不想遍历原型链的属性时,可采用如下方法

// in 是会攀升原型链的,比较消耗性能
    for(const key in a) {
        if(a.hasOwnProperty(key)) {
            consle.info(a[key]);
        }
    }

把原型链借给我用用呗


  • 此处不纠结js有原生Math.max()方法,单纯用于理解利用call和apply借用其他原型链上面的方法
// apply 
let hd = {
    arr: [1,2,4,5,69,9,5]
};
Object.setPrototypeOf(hd, {
    max() {
        return this.arr.sort((a, b) => b - a)[0];
    },
});
let xj = {
    lessons: { js: 87, php: 34, node: 50, linux: 99},
    // getter
    get arr() {
        return Object.values(this.lessons);
    }
};
console.info(hd.max.apply(xj)); // 99
// call

 let hd = {
        arr: [1,2,4,5,69,9,5]
    };
    Object.setPrototypeOf(hd, {
        max(arr) {
            return arr.sort((a, b) => b - a)[0];
        },
    });
    let xj = {
        lessons: { js: 87, php: 34, node: 50, linux: 99},
    };
    console.info(hd.max.call(null,Object.values(xj.lessons)));

DOM节点借用Array原型方法


let btns = document.querySelectorAll('buton');
btns = Array.prototype.filter.call(btns, item => {
    return item.hasAttribute('calss');
});
console.info(btns);

一些小问题


  • 如何定义一个没有原型的对象

    可以通过Object.creat(null, {});来创建

  • 原型方法与对象方法的优先级

    对象方法 > 原型方法

  • this与原型没有很大关系,永远指向调用其属性的对象

  • 不是很建议在系统的原型中追加方法,容易造成后期维护代码的不稳定

  • 改变构造函数原型并不是继承