原型 & 原型链 & ES6 的 class

理解 constructor & prototype(显式原型属性) & __proto__(隐式原型属性)

以👇为例理解 constructor & prototype & __proto__ 之间的关系

function People(name) {
    //隐式的执行了:
    // var this = {__proto__: People.prototype};
    this.name = name;
     //隐式的执行了:
    // return this;
}

// People.prototype = { constructor: People }; //这是在 People 函数被定义的时候,就有的~
// 原型对象也是对象,所以它的值中应该包含 __proto__ 属性,People.prototype = { constructor: People, __proto__: Object.prototype}; //这是在 People 函数被定义的时候,就有的~

People.prototype.sayName = function() {
    console.log('this.name --> %s', this.name);
}

var people1 = new People('张三');
var people2 = new People('李四');

people1.__proto__ === People.prototype; //true
people1.constructor === People.prototype.constuctor; //true
people1.__proto__ === people2.__proto__; //true

即:
1. 每个构造函数都有一个原型属性 (eg. People.prototype)。
2. 每个实例都有 __proto__ 属性, __proto__ 属性指向了实例构造函数的原型 (eg. people1.__proto__)。
3. 原型也是一个对象,是函数(Funciton)的一个属性。
4. 通过构造函数产生的对象,可以继承构造函数以及其原型上的属性和方法。
  1. 每个构造函数都有一个原型属性 (eg. People.prototype)。
  2. 原型也是一个对象,是函数(Funciton)的一个属性。
  3. 所有函数都有__proto__ 属性(函数是特殊的对象),函数(不管是内置的还是自定义的函数)的 __proto__ 都指向 Function.prototype。
  4. 每个实例都有 __proto__ 属性, __proto__ 属性指向了实例构造函数的原型 (eg. people1.__proto__)。
  5. 通过构造函数产生的对象,可以继承构造函数以及其原型上的属性和方法。
  6. 构造函数特点:大驼峰式
  7. 隐式和显式都是对属性的修饰,它们指向的对象都叫做原型对象

PS:

  1. __proto__ 并不是语言本身的特性,而是各大厂商添加的私有属性,所以尽量避免使用,
  2. 如果想获取实例对象的原型 可以通过 Object.getPrototypeof(实例对象)

关于 prototype:

每一个函数天生自带一个属性叫做 prototype ,他是一个对象

只要函数定义好了以后,这个 prototype 就出生了

构造函数也是函数,也有prototype,可以向prototype对象里面添加内容

这个天生自带的prototype里面有一个属性,叫做 constructor => 表示是哪一个构造函数伴生的原型对象

关于 __proto__ :

每一个对象天生自带一个属性,叫做 __proto__ , 指向所属构造函数的 prototype

实例化对象也是一个对象

实例化对象也有 __proto__ 属性

对象访问机制:

**********

当访问一个对象的属性成员的时候,现在对象身上找,

找不到再到对象的__proto__上去找

再没有,再去__proto__上去找(即去找构造函数的prototype对象的__proto__)

一直找到顶级对象的 __proto__ 都没有,就返回 undefined

***********

先访问对象的直接属性或者方法,如果没有,访问对象的__proto__(即构造函数的prototype对象)上是否存在这个属性或者方法

prototype的作用:就是为了书写一些方法给构造函数的实例对象使用 

=> 因为这个构造函数的每一个实例都可以访问这个构造函数的 prototype。

问题 :

1. 每一个构造函数都有 prototype , prototype是一个对象,所以prototype对象也有 __proto__ 属性,它指向谁?

2. 构造函数也是函数,函数也是一个对象,所以函数本身也有__proto__属性,它指向谁?

例子:
1. var arr = []; Array 的实例
2. var obj = {}; Object 的实例
3. var p1 = new Person(); Person 的实例
4. var time = new Date(); Date 的实例
5. var fn = function() {}; Function 的实例
6. Person.prototype  Object 的实例
7. Array.prototype   Object 的实例

************************结论***************************

从任何一个对象开始出发,

按照 __proto__ 开始向上查找,最终都能找到 Object.prototype,

我们管这个使用 __proto__ 串联起来的对象链状结构,叫做原型链。

原型链作用:为了对象访问机制服务。

************************重要结论**************************

1. 每一个函数天生自带一个属性叫做 prototype, 是一个对象。

2. 每一个对象天生自带一个属性叫做 __proto__ , 它指向所属构造函数的prototype。

3. 当一个对象,没有准确的构造函数来实例化的时候,我们都看作是内置构造函数 Obj ect 的实例。

4. prototype里面的constructor属性(构造器):只有函数天生自带的prototype上才有(自己手动添加的没有),表示是哪一个构造函数所自带的原型对象。作用:判断数据类型。

只要是函数 就是 Function 的实例 

Object.prototype 是顶级原型对象

内置构造函数 Object 是内置构造函数 Function 的一个实例

函数的顶级是内置构造函数 Function

对象是谁(哪个构造函数)的实例,它的__proto__就指向谁的原型对象(构造函数的prototype)

原型链的顶端是 Object.prototype    

Object.prototype.__proto__   === null    是一个空对象

关于原型和原型链的面试

什么是原型?

原型:是每一个函数天生自带的一个空间(属性),原型里定义的属性和方法构造函数的所有实例都可以使用(里面的方法是为了让实例使用的,多个实例共享方法的)。

什么是原型链?

原型链:从任何一个对象出发,按照__proto__串联起来的对象链状结构,为了对象访问机制而存在。

PS: 原型和原型链没有必然的联系

ES6 中的 class :

ES6 中的 class 可以看作是一个语法糖,它的绝大部分功能 ES5 都能做到。

class People{
    constructor(name) {
        this.name = name;
    }

    sayName() {
        console.log('this.name --> %s', this.name);
    }
}

等同于 👇
People.prototype = {
    constructor() {},
    sayName() {}
}

ES6 的 class:

1. 与ES5中类的不同之处

1.

  • ES6 中类的内部定义的方法是不可枚举的
  • ES5 中类的内部定义的方法是可枚举的

eg.

// ES6 
class Point {
  constructor(x, y) {
    // ...
  }

  toString() {
    // ...
  }
}

Object.keys(Point.prototype)
// []
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

// ES5
ar Point = function (x, y) {
  // ...
};

Point.prototype.toString = function () {
  // ...
};

Object.keys(Point.prototype)
// ["toString"]
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

2. ES6 中 类必须使用 new 调用,否则会报错(ES5 中类可以像普通函数一样直接被调用,ES6中直接调用类会报错)

3. ES6 中 类不存在变量提升

2. ES6 class 中的静态方法

  1. 在方法前面加上 static 关键字,表明方法是静态方法。
  2. 静态方法不会被实例继承,而是直接通过类来调用。()
  3. 静态方法可以与非静态方法重名。
  4. 父类的静态方法可以被子类继承。
  5. 子类中可以通过 super 对象来调用父类的静态方法
    eg.
    class Foo {
      static classMethod() {
        return 'hello';
      }
    }
    
    // Foo.classMethod() -->静态方法直接通过类调用
    // 静态方法也可以看作是构造函数上的一个方法,可以在类内部的非静态方法中通过
    // this.constructor.classMethod 来调用
    
    class Bar extends Foo {
      static classMethod() {
        return super.classMethod() + ', too';
      }
    }
    
    Bar.classMethod() // "hello, too"

注意:

  • 静态方法不能被实例继承,但能被子类继承。
  • 如果静态方法中使用了 this 关键字,this 指的是类,而不是 实例
  • 静态方法中只能调用静态方法,不可以调用非静态方法。

ps: 静态方法也可以看作是构造函数上的一个方法,可以在类内部的非静态方法中通过 this.constructor.classMethod 来调用(this指向实例实例时)

3. ES6 class 中实例属性的写法:

两种方式:
第一种:在 constructor 里面定义
class Test1{
    constructor() {
        this.count = 0;
    }
}

第二种:在 class 的顶部定义
这样写的好处:
所有类的实例的属性都写到顶部,比较整齐,一眼就能看出实例可以拥有哪些属性,也比较简洁而
class Test2{
    _count = 0; //注意不要写 this!
    constuctor() {}
}

4. ES6 class 中的静态属性:

静态属性:定义在 class 类上面的属性,而不是定义在实例上面的属性。

  1. 静态属性不会被实例继承,但可以被子类继承。

在变量前面加上 static , 并且需要采用变量写到class顶部的方式 (但其实也只是提案,如果想要使用,可以通过 babel 编译)

eg.

class Test{
  static _count = 0;
  constructor() {

  }
}

var t = new Test();
console.log('t._count :%s', t._count);//undefined
console.log('Test._count', Test._count);//0

另一种方式(老写法):

class Foo{
    constructor() {}
}
Foo._count = 0; //易被忽略

5. 私有属性/方法

私有属性/方法:只能在类的内部使用的属性/方法。

1. 通过变量命名,约定以_开头的属性/方法是私有的。

2. 通过 Symbol 

onst bar = Symbol('bar');
const snaf = Symbol('snaf');

export default class myClass{

  // 公有方法
  foo(baz) {
    this[bar](baz);
  }

  // 私有方法
  [bar](baz) {
    return this[snaf] = baz;
  }

  // ...
};

3. 一个私有属性/方法的提案:在私有属性/方法前面加上“  #  ”

  1. 注意: 私有属性/方法 只能在类的内部使用,实例不可以直接使用 
  2. 私有属性/方法前可以添加 static , 即变为 静态私有属性/方法,这样就只能通过类调用静态私有方法,只能通过类访问静态私有属性
class Foo {
  #a;
  #b;
  constructor(a, b) {
    this.#a = a;
    this.#b = b;
  }
  #sum() {
    return this.#a + this.#b;
  }
  printSum() {
    console.log(this.#sum());
  }
}

var foo = new Foo('a', 'b');
foo.#a // 报错,因为#a是类的私有属性,实例不可以访问
foo.#sum() // 报错,因为#sum是类的私有方法,实例不可以调用

ps: 其他实现私有变量的具体方法 CSDN (除了提案的其他方式)

6. ES6的新属性 new.target 

new.target 作用:用来确定构造函数是怎么调用的(指向当前正在运行的函数)

如果构造函数不是通过new命令或Reflect.construct()调用的,new.target会返回undefined

ps:只能在函数内部使用

7. 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值