碎碎念
小米扫地机器人没有抢到。。。。这些人太快了。
昨天写完文章后,为了等时间抢购,玩了三把第五人格,运气不好匹配到了红蝶,被闪现技能吓得心脏都要跳出来。
强烈建议不要有这个技能,吓死人不偿命啊。现在过了一个晚上回想起来还觉得心有余悸,可怕!!!三个严重的叹号。
前言
回归正题。原型
、原型链
又是前端开发人员另外一个圣神的话题。初级会使用这个封装一个组件,几乎是你进阶的标志。
对象
对象是js的基础,js中一共有6中基本类型:
- string
- number
- boolean
- null
- undefined
- object
其中有个特殊的,typeof null === 'object'
。也不用纠结,算是一个小问题。
原理上是不同的对象在底层都表示为二进制,在JavaScript
中二进制前三位都为0的话会被判断为object类型,null的二进制表示是全0,自然前三位也是0,所以执行typeof
时会返回“object”。
js还有对象子类型
,通常被称为内置对象
。
- String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
- Error
这些内置函数可以当作构造函数来使用,从而可以构造一个对应子类型的新对象。
需要注意的是var str = 'i am working'
,我们定义了一个str,现在我们尝试着str.length
,这个length
实际上是js把str
隐性得转成了String对象
,length
也是String对象
的方法。
类
抽象点讲,类是一个蓝图,基础实现。类实例化或者被继承的时候,会把自己的特性复制到实例或者子类中。js里是没有类的,只有对象。
原型
1 | // 这个Foo的prototype就是个原型 |
[[prototype]]
/ __proto__
js在声明一个对象的时候,都会给这个对象的默认加一个[[prototype]]
属性,它用对其他对象的引用,也可以认为你的父级吧。1
2
3
4var obj = {
a: 1
}
obj.a //1
当我去访问a变量
的时候,会隐性触发一个Getter
,这个Getter
会先从当前作用域找a
,找不到就会顺着这个[[prototype]]
引用的上级对象找a
,最后找不到再返回undefined
。这种关联定义了原型链
。[[prototype]]
链的尽头是Object.prototype
。
这个时候我们在浏览器控制台声明一个变量obj,打印obj,会发现obj有一个__proto__
,这个就是上面说的内置属性,表现形式为这样而已,方便展示出来给开发者看。
[[prototype]]
/ prototype
往往拿来比较的还有一个内置属性叫prototype
。这个比较特殊,它只有在函数对象才会有,它是拿来指向这个函数对象的原型对象。1
2
3function Foo(){
}
Foo.prototype
Foo.prototype
就是Foo
的原型,顺带扯一下,Foo.prototype
里有内置一个constructor
,指向Foo
,Foo
是Foo.prototype
的构造器函数
。
构造函数
上面介绍了啥是构造函数1
2
3
4
5
6
7
8
9
10function Foo(){
}
// 到这里为止,Foo还是一个普通函数
var a = new Foo();
// 等价于下面的
// var a = {};
// a.__proto__ = Foo.prototype;
// Foo.call(a);
// Foo是构造函数
这里Foo
就是一个构造函数(函数对象),a
是一个Foo的实例对象(普调对象),在new的过程中,给a内置了一个[[prototype]]
这里是__proto__
,指向了Foo.prototype
。
所以这里一定程度上实现了继承
,但是类的继承是属于复制的行为,但是js对象默认是不会复制的,所以new
的过程,给两个对象做了关联,一定程度上可以让a使用Foo的方法,这就一定程度上实现了继承。
所以这个a.__proto__
就全等于Foo.prototype
,因为它们引用的是同一个东西。a.__proto__
的constructor
属性也被委托到了Foo.prototype
,所以a.__proto__.constructor === Foo.prototype.constructor
。
来张图感受一下:
继承
1 | function Foo(name) { |
这就典型的继承了原型上的myName
的方法。
1 | function Foo(name) { |
这段代码的核心部分就是语句 Bar.prototype = Object.create()
Object.create(..) 会凭空创建一个“新”对象并把新对象内部的[[Prototype]]
指向到你指定的对象(本例中是 Foo.prototype)。
讲这么多就是说,如何实现继承,就是要改成这个内置属性的指向。
如果能有一个标准并且可靠的方法来修改对象的[[Prototype]]
关联就好了。
在ES6之前, 我们只能通过设置__proto__
属性来实现,但是这个方法并不是标准并且无法兼容所有浏览器。
ES6添加了辅助函数Object.setPrototypeOf(..)
,可以用标准并且可靠的方法来修改关联。
我们来对比一下两种把Bar.prototype
关联到Foo.prototype
的方法:
ES6之前需要抛弃默认的Bar.prototype
Bar.prototype = Object.create( Foo.prototype );
ES6开始可以直接修改现有的Bar.prototype
Object.setPrototypeOf( Bar.prototype, Foo.prototype );
1 | var foo = { |
1 | // es5 的写法 |
Class
es6新增了Class
语法,通过extends
就可以简单的实现继承。1
2
3
4
5
6
7
8
9
10class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
感觉es6出现真的是方便多了,像箭头函数啊,多好用,都不用想this
,bind它。
这里有个super
,在这里表示父类的构造函数,用来新建父类的this对象。
子类必须在constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super
方法,子类就得不到this对象
。
因此super()
在这里相当于Point.prototype.constructor.call(this)
。
ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
super
还有个功能就是作为对象使用,代表父级。你可以super.toString()
总结
怎么写的这么语无伦次