2. javascript中的原型原型链

一、理解原型对象

1、每一个函数数据类型的值都有一个天生自带的属性:prototype 这个属性的属性值是一个对象("用来存储实例公用的属性和方法的")

  • 普通函数
  • 类(自定义类和内置类)

2、在prototype这个对象中,有一个天生自带的属性constructor,当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。

Fn.prototype.constructor === Fn

3、每一个对象数据类型的值,也有一个天生自带的属性:__proto__,这个属性指向所属类的prototype;

2. 原型存在的意义

当构造函数中添加方法的时候,每new一次就生成一个同类方法,这些方法虽然同类但是各自不相等,这就导致如果new若干次,就会生成若干个一摸一样的方法,这样对性能是不友好的,所以我们要使用原型的方式去把方法挂在原型上

3. 原型查找机制

1、先找自己私有的属性,有则调取使用,没有继续找;

2、基于__proto__找所属类原型上的方法(Fn.prototype), 如果还没有则继续基于__proto__往上找,一直找到Object.prototype为止;

image.png

原型原型链的查找模式
image.png

4.重构类的原型
重构类的原型:让某个类的原型指向新的堆内存地址(重定向指向)

  • 问题1:重定向后的空间中不一定有constructor属性(只有浏览器默认给prototype开辟的堆内存中才存在constructor),这样导致类和原型机制不完整;所以需要我们手动再给新的原型空间设置constructor属性;

  • 问题2:在重新指向之前,我们需要确保原有原型的堆内存中没有设置属性和方法,因为重定向后,原有的属性和方法就没用了(如果需要克隆到新的原型堆内存中,我们还需要额外的处理)

    =>但是内置类的原型,由于担心这样的改变会让内置的方法都消失,所以禁止了我们给内置类原型的空间重定向,例如:Array.prototype={...}这样没有用,如果想加方法Array.prototype.xxx=function(){...}可以这样处理
    如下

function Fn() {
    // ...
}
Fn.prototype.xxx = function () {}
//=>批量给原型设置属性方法的时候:重构类的原型
Fn.prototype = {
    constructor: Fn,
    getA: function () {},
    getB: function () {}
}; 

//=>批量给原型设置属性方法的时候:设置别名
let proto = Fn.prototype;
proto.getA = function () {}
proto.getB = function () {}
proto.getC = function () {}
proto.getD = function () {}

我们需要清楚知道的是函数的三种角色

  • 普通函数
    let total = Fn(20, 10);
  • 构造函数(类和实例)
    let f = new Fn(20, 10);
  • 普通对象
    Fn.total = 1000;

如图:


函数的三种角色.png

接下来我们来看一个基于原型原型链查找的案例

实现hasOwnProperty
检测某一个属性名是否为当前对象的私有属性;

"in" :检测这个属性是否属于某个对象(不管是私有属性还是共有属性,结果都为TRUE);

let ary = [10,20,30];
console.log('0' in ary);      // true
console.log('push' in ary);    //true
console.log(ary.hasOwnProperty('0')); // TRUE
console.log(ary.hasOwnProperty('push'));  // FALSE

自己堆中有的属性就是私有属性,需要基于_proto_查找的就是共有属性(_proto_在IE浏览器中保护起来,不让我们在代码中操作它);

/*基于内置类原型扩展方法
*检测某个属性是否为对象的公有属性:hasPubProperty
*方法:是它的属性,但是不是私有的
*/
Object.prototype.hasPubProperty = function (property) {
    //=>验证传递的属性名合法性(一般只能是数字或字符串等基本值)
    let x = ["string", "number", "boolean"],
        y = typeof property;
    if (!x.includes(y)) return false;
    //=>开始校验是否为公有的属性(方法中的THIS就是要校验的对象)
    let n = property in this,
        m = this.hasOwnProperty(property);
    return n && !m;
}
console.log(Array.prototype.hasPubProperty('push')); //=>FALSE
console.log([].hasPubProperty('push')); //=>TRUE

下面我们来看一道简单的题目

//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);
console.log(f1.getY === f2.getY);
console.log(f1.__proto__.getY === Fn.prototype.getY);
console.log(f1.__proto__.getX === f2.getX);
console.log(f1.getX === Fn.prototype.getX);
console.log(f1.constructor);
console.log(Fn.prototype.__proto__.constructor);
f1.getX();
f1.__proto__.getX();
f2.getY();
Fn.prototype.getY();

基于constructor实现数据类型检测就是这样来玩的
但是这种方式有很大的弊端:因为用户可以去随意修改对应的constructor值或者是手动给ARY增加一个私有的constructor属性等


//2、
function C1(name) {
    if (name) { 
        this.name = name;
    }
}
C1.prototype.name = 'Tom';

function C2(name) {
    this.name = name;
}
C2.prototype.name = 'Tom';

function C3(name) {
    this.name = name || 'join';
}
C3.prototype.name = 'Tom';

alert((new C1().name) + (new C2().name) + (new C3().name)); 

//3.
function Fn() {
    this.x = 100;
    this.y = 100;
}
Fn.prototype.getX = function () {
    console.log(this.x);
}
let f1 = new Fn;
Fn.prototype = {
    getY: function () {
        console.log(this.y);
    }
};
let f2 = new Fn;

// 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();
Foo().getName();
getName();
new Foo.getName(); //=>Foo.getName:输出2这个方法(AF2)  =>new AF2()
new Foo().getName();
new new Foo().getName();

你可能感兴趣的:(2. javascript中的原型原型链)