函数的prototype属性(原型对象)

1.函数的prototype属性

每个函数都有一个prototype属性,它默认是一个空的Object的实例对象(即称为:原型对象),而原型对象中有一个属性constructor,它指向函数对象。这说明(构造)函数和它的原型对象相互引用

Type表示的是构造函数,它其中有个属性prototype,而这个属性指向这个函数的prototype对象,而这个对象有个属性constructor,它是指回这个构造函数Type的,即是相互引用关系。

1.1 代码体验

<script>
    // 1. 每个函数都有一个prototype属性,它默认指向一个Object空对象(即称为:原型对象)
    // (1) 内置对象(构造函数)
    // 打印结果是object,但不是空对象,原因是系统内部先前已为其添加了各种属性,但它本身原始是空对象
    console.log(Date.prototype);
    // (2) 自定义对象(构造函数)
    function Fun() {

    };
    console.log(Fun.prototype); // 默认指向Object的空实例对象(没有程序员自己指定的属性)
    // 2. 原型对象中有一个属性constructor(默认内置的属性),它指向函数对象
    console.log(Date.prototype.constructor === Date); // true
    console.log(Fun.prototype.constructor === Fun);   //true
</script>

注意:任何一个函数都有prototype属性,只是我们平时一般只有在使用构造函数时才会提及使用它而已,切勿误解。

2. 显示原型和隐式原型

①  每个函数function都有一个prototype,即显式原型
②  每个实例对象都有一个__proto__,可称为隐式原型
③  对象的隐式原型的值为其对应构造函数的显示原型的值(显式原型===隐式原型)

2.1 代码体验

<script>
    // 1. 定义构造函数
    function Fn() {  // 内部执行语句:this.prototype={}

    };
    // (1)每个函数function都有一个prototype,即显式原型属性,默认指向一个空的object对象
    console.log(Fn.prototype);
    // 2. 创建一个实例对象
    let fn = new Fn();   // 内部执行语句:this.__proto__ = Fn.prototype
    // (2)每个实例对象都有一个__proto__,即隐式原型属性
    console.log(fn.__proto__);
    // (3)对象的隐式原型的值为其对应的构造函数的显式原型的值
    console.log(fn.__proto__ === Fn.prototype);  // true
    // 3. 给显式原型(原型对象) 添加属性(一般是方法)
    Fn.prototype.test = function() {
        console.log('test()');
    };
    // 4. 通过实例对象可调用原型对象的方法
    fn.test();  // 打印结果 test()
</script>

总结:

① 函数的prototype属性:在定义函数时自动添加的,默认值是个空的Object的实例对象

② 对象的__proto__属性:创建对象时自动添加的,默认值为构造函数的prototype属性值

③ 程序员能直接操作显式原型(添加属性(一般为方法)==》实例对象可访问),但一般不直接操作隐式原型。

2.2 内存结构图

为了方便了解原型,以及显式和隐式原型的区别,可以结合下面图片加强理解。

 3. 手写数组函数

在了解了原型的相关知识后,我们可以对前面所学的一些数组方法,自己手写函数封装一样功能的方法,从而更好的理解原型的概念和使用。

<script>
    // 给内置构造函数Array的原型对象上添加自己书写的方法,这样使用数组时就可以调用该方法了
    // (1) 过滤方法
    Array.prototype.myFilter = function(callback) {
        let narr = [];
        for (let i = 0; i < this.length; i++) {
            if (callback(this[i])) {
                narr.push(this[i])
            }
        }
        return narr;
    };
    // (2) 映射方法
    Array.prototype.myMap = function(callback) {
        let narr = [];
        for (let i = 0; i < this.length; i++) {
            narr.push(callback(this[i]));
            callback()
        };
        return narr;
    };
    // (3) 累计方法
    // num表示初始值
    Array.prototype.myReduce = function(callback, num) {
        // sum表示计算结束后的返回值。
        let sum = num;
        for (let i = 0; i < this.length; i++) {
            sum = callback(sum, this[i])
        };
        return sum
    }
    let arr1 = [11, 22, 33, 44, 55, 66, 77, 88, 99, 123, 456];
    // 由于上面我给内置对象Array的原型对象上添加了3个新方法,所以下面的实例对象就可以直接使用
    let arr2 = arr1.myFilter(r => r % 2 === 0);  // 从原数组中过滤出一个只含奇数的新数组
    console.log(arr2);
    let arr3 = arr1.myMap(r => r + 1);  // 从原数组中映射出一个各元素的值均大1的新数组
    console.log(arr3);
    let arr4 = arr1.myReduce((a, b) => {  // 将原数组各元素的值累加为一个值(即求和)
        return a + b
    }, 0);
    console.log(arr4);  // 1074
</script>

4. 实际应用

正因为我们程序员可以手动给函数的原型对象添加属性(方法),所以我们在实际开发中,创建构造函数时,如果需要创建大量实例化对象,那么把方法添加到函数的原型对象prototype中,这样可以减少内存空间,从而大大提高性能。 

<script>
    function Student(name, age, sex) {
        // 给构造函数添加属性,要使用this,这个this是指向实例化对象的
        // 普通属性我们可以直接添加即可,属性值以形参的形式传入,这样具有扩展性
        this.name = name
        this.age = age
        this.sex = sex
    };
    // 方法不直接添加,而是将其添加在函数的原型对象上,这样函数的所有实例对象自动拥有原型中的方法, 
    // 从而可以减少内存空间,从而提高性能
    Student.prototype.sayHi = function() {
        console.log(`我叫${this.name},今年${this.age}岁,性别是${this.sex}`);
    };
    Student.prototype.study = function(time) {
        console.log(`我叫${this.name},我每天学习${time}小时`);
    };
    // 创建多个实例化对象时,直接使用原型方法,并且因为使用了this指向,所以打印的内容都是活泛的
    let s1 = new Student("张三", 22, "男");
    s1.sayHi();
    s1.study(10);
    let s2 = new Student("李四", 34, "男");
    s2.sayHi();
    s1.study(8);
    let s3 = new Student("王燕", 29, "女");
    s3.sayHi();
    s3.study(12);
</script>

猜你喜欢

转载自blog.csdn.net/JJ_Smilewang/article/details/125674408
今日推荐