理解 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. 通过构造函数产生的对象,可以继承构造函数以及其原型上的属性和方法。
- 每个构造函数都有一个原型属性 (eg. People.prototype)。
- 原型也是一个对象,是函数(Funciton)的一个属性。
- 所有函数都有__proto__ 属性(函数是特殊的对象),函数(不管是内置的还是自定义的函数)的 __proto__ 都指向 Function.prototype。
- 每个实例都有 __proto__ 属性, __proto__ 属性指向了实例构造函数的原型 (eg. people1.__proto__)。
- 通过构造函数产生的对象,可以继承构造函数以及其原型上的属性和方法。
- 构造函数特点:大驼峰式
- 隐式和显式都是对属性的修饰,它们指向的对象都叫做原型对象
PS:
- __proto__ 并不是语言本身的特性,而是各大厂商添加的私有属性,所以尽量避免使用,
- 如果想获取实例对象的原型 可以通过 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 中的静态方法
- 在方法前面加上 static 关键字,表明方法是静态方法。
- 静态方法不会被实例继承,而是直接通过类来调用。()
- 静态方法可以与非静态方法重名。
- 父类的静态方法可以被子类继承。
- 子类中可以通过 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 类上面的属性,而不是定义在实例上面的属性。
- 静态属性不会被实例继承,但可以被子类继承。
在变量前面加上 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. 一个私有属性/方法的提案:在私有属性/方法前面加上“ # ”
- 注意: 私有属性/方法 只能在类的内部使用,实例不可以直接使用
- 私有属性/方法前可以添加 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.