个人自学前端19-JS12-类和原型和原型链

类和原型和原型链

一 类

面向对象里,有一个非常基础的概念,就是类。

类就是拥有相同特征的一类对象的集合。

我们已经接触过的 js 原生类有很多。

例如:Array,Date,Object等。

类是对象的模板,对象是类的实例。

为什么需要使用类?它有什么用?(很抽象)

类可以归类多个数据,方便编程时获取.

方便给多个对象添加相同的方法.

现实生活中,其实类的影子比比皆是.

比如我们说鸟,这里鸟是一个类.

说到麻雀鸟,这是鸟类的一个对象,一个实例.

这里有一个很有意思的哲学问题,可能可以帮助你理解什么是类.

请思考,下面的命题是否是正确的.

1:世界上没有马。

2:世界上没有水果。

3:世界上没有人。

以上命题在哲学角度,都是对的。

如果世界上有马,请牵一匹马过来,你一定牵不来一匹马,你牵来的一定是白马或者黑马。

如果世界上有水果,请拿一个水果来,你一定拿不来水果,你拿来的一定是香蕉或者苹果之类。

如果世界上有人,请找来一个人,你一定找不来一个人,你找来的一定是男人或者女人.

以上说的马,水果,人,的确是不存在的,之所以人们能知道马的样子,是因为看过了很多的马总结出来的。

这里马,水果,人就是类,白马黑马是马这个类的对象。苹果香蕉是水果类的对象。男人女人是人类的对象。

类都是抽象的,看不见的,对象都是看得见的,摸得着的。

js 中的Array就是一个类,它表示所有的数组这个集合。它是看不见的,抽象的。

我们能看得见的是一个个的数组实例。例如:[1,2,3],[4,5,6] 等等.

我们认知中所有的数组,构成了一个看不见的抽象的数组类.

类的两个概念:类的私有属性,类的公有方法。

人是一个类,每个人的名字各不一样,则名字就是类的私有属性。

每个人都会说话,这个行为每个人都是一致的,这个应该是类的公有方法。

数组是一个类,每个数组的length各不相同,则length就是数组的私有属性。

每个数组实例都可以通过push方法把一个元素放到数组的最后面,这个push应该是数组类的公有方法。

是人都会说话,只要你是人就会说话。

数组都可以push,只要你是数组,就可以push。

类有归纳总结的作用。

那如何给一个类添加一些自定义的公有方法呢?

二 原型

类的原型:类实例共同特征的集合。(类的公有方法的集合)

所有的人的共同特征:会说话,会穿衣,会使用工具等等。

人所有的这些共同特征的集合就构成了人类的原型。

以数组为例:数组都可以push,pop,shift,unshift,splice等,则所有这些方法,就构成了数组类的原型。

// 原型其实就是类的一个属性。
console.log(Array.prototype);
// 数组原型的数据类型是对象。这个对象上的所有属性,就是所有数组的公有方法。所有的数组都可以使用。
console.log(typeof Array.prototype); // object

这个原型有什么用?可以通过这个原型,给所有的数组实例添加一些自定义的公有方法。

// 给原型对象添加一个fn方法,则所有的数组实例都可以使用这个方法。
Array.prototype.fn = function(){console.log(100)};
[1,2,3].fn();
[4,5,6].fn();
公有属性存储在哪里 => 存储在原型对象上.
系统默认的方法,没有prototype属性.
自定义的方法有prototype属性.有prototype属性意味着fn可以实现一个自定义类.(面向对象)

如何获取一个对象的上一级原型对象

let oDiv = document.getElementById('wrap');
console.log(oDiv.__proto__);

    // 如果我想给所有的div标签都添加一个公有方法.

    // oDiv 
    // => HTMLDivElement.prototype
    // => HTMLElement.prototype
    // => Element.prototype
    // => Node.prototype
    // => EventTarget.prototype
    // => Object.prototype

三 原型链

3.1 继承

龙生龙,凤生凤,老鼠生儿会打洞。这其实就是一种继承现象。

北海龙王继承龙类。黑凤继承凤类。锦毛鼠继承鼠类。

所有数组实例默认都可以push,这也是一种继承现象。[1,2,3]继承Array。

js 的继承有点奇葩,实例不是继承类,而是继承类的原型。

对于某个实例,可以通过属性proto来访问它继承的是什么对象.

console.log([1,2,3].__proto__);
console.log([1,2,3].__proto__ == Array.prototype); // true

数组继承数组的原型,以此类推.

所有的函数组成一个类Function,因此所有的数组继承Function.prototype;

所有的纯对象组成一个类Object,因此所有的纯对象继承Object.prototype;

继续推理:

那 Array.prototype 继承什么呢?

Array.prototype 本身就是一个纯对象,因此 Array.prototype 是一个纯对象实例,它默认继承 Object.prototype

console.log(Array.prototype.__proto__ == Object.prototype); // true

梳理一下:

[1,2,3] 继承 Array.prototype,Array.prototype 继承 Object.prototype。这类似于 子 => 父 => 祖父的关系. 即:

[1,2,3] => Array.prototype => Object.prototype ;

这种关系,组成了一个 "链条",这种关系,称之为 "原型链"。(有点类似作用域链)

原型链描述的就是一种继承上的关系。

[1,2,3]继承Array.prototype,可以访问Array.prototype上的方法。

Array.prototype 继承 Object.prototype,可以访问 Object.prototype 上的方法。

则 [1,2,3] 实际上也继承 Object.prototype,也可以访问 Object.prototype上的方法。

Object.prototype.fn = function(){console.log(200)};
[1,2,3].fn();
[4,5,6].fn();

知道了数组的原型链,可以推论出其他数据类型的原型链。

函数原型链:alert => Function.prototype => Object.prototype

纯对象原型链: {} => Object.prototype

通过分析这些原型链,可以得出结论,在Object.prototype上添加的方法,数组,函数,纯对象都可以访问。

Object.prototype.fn = function(){console.log(200)};
[1,2,3].fn();
alert.fn();
window.fn();

这种关系有点类型于:

生物类

动物类

哺乳类

鸟类

鱼类

植物类

松科

生物类的特征是需要氧气,因此所有的子类都需要氧气。

比较一下:

Object.prototype

Array.prototype

[1,2,3],[4,5,6]

Function.prototype

alert,parseInt

Object.prototype 拥有fn方法,因此所有的子类和子类实例都可以访问。
所有原型链的顶层对象都是 Object.prototype (null).
任何类的实例,都可以访问Object.prototype的方法.

四 私有属性

属性可以分为私有属性和公有属性
1:私有属性.(自定义属性)(手动添加)(存储在对象身上)
2:公有属性(继承属性)(不手动添加,默认就有的属性)(不存储在对象身上)

方法也分私有和公有
1:私有方法(自定义,手动添加的方法)
2:公有方法(继承方法)(默认就有的方法,默认就能使用的方法)

如何判断一个属性是不是私有的.

hasOwnProperty => 判断是不是私有属性的.
1: 用来干嘛 => 判断一个属性是不是某个对象的私有属性
2: 参数就是要检测的属性名
3: 返回布尔值
4: 对象.hasOwnProperty(属性名);

    const obj = { name: '幂幂' };

    // 判断name属性是不是obj的私有属性。
    console.log(obj.hasOwnProperty('name'));
    // 判断toString属性是不是obj的私有属性。
    console.log(obj.hasOwnProperty('toString'));
    // in => 可以检查一个对象能不能访问某个属性(方法)
    if (!obj.hasOwnProperty('toString') && 'toString' in obj) {
      alert('toString是公有方法')
    }

五 原型链与作用域链

作用域链 => 非面向对象 => 确定某个作用域内能访问到的变量,作用域链确定了,能访问到的变量也就确定了.

原型链 => 面向对象 => 确定某个实例能访问到的属性(方法),原型链确定了,能访问到的属性(方法)就确定.

作用域链 => 实现变量查找
1:先写除作用域链.
2:沿着作用域链查找,查找变量声明,找到就停止查找.(就近原则).找到全局作用域,如果还没有就报错.

原型链 => 实现属性查找
1:先写原型链.
2:沿着原型链查找,查找当前的原型对象有没有对应的私有属性,有就停止查找(就近原则),找到Object.protorype,如果还没有,得到undefined.

    let arr = [1,2,3];
    console.log(arr.abc);
    // arr => Array.prototype => Object.prototype

    // 1: 先看看arr有没有abc私有属性.如果有就是它,停止往上查找
    // 2: 如果arr没有abc私有属性,看看Array.prototype有没有abc私有属性.如果有就是它,停止往上查找
    // 3: 如果Array.prototype没有abc私有属性,看看Object.prototype有没有abc私有属性.如果有就是它.没有就返回undefined
    // in => 检查对应的属性是不是存在于实例的原型链上.
    let obj = {name: '幂幂'};
    Object.prototype.age = 32;
    // for in 可以检测变量实例原型链上的可枚举的属性.
    // 可枚举 => 可for in.
    for (let key in obj) {
      console.log(key);
    }
posted @ 2021-07-22 16:57  暗鸦08  阅读(85)  评论(0)    收藏  举报