四、JavaScript类
JavaScript本身就是面向对象的,所以它并不需要模拟面向对象,只是它面向对象的方式不是通过类,而是基于原型系统来面向对象。也即是JavaScript是一个基于原型的编程语言。
(一)那既然如此,什么是原型呢?
1.基于原型的编程更关注对象实例的行为,然后才去关心如何将这些对象划分到使用方式相似的原型对象,而不是将它们进行分类。
2.基于原型的对象系统通过“复制”的方式来创建新对象。
3.JavaScript采取的原型系统的复制操作是:并不真的是去复制一个原型对象,而是使新对象持有一个原型的引用。(JavaScript高级编程中指出,当从一个变量向另一个变量复制引用类型的值的时候,同样也会将存储在变量对象中的值复制一份放在为新变量分配的空间。不同的是,这个值的副本实际上是一个指针,而这个指针指向存放在堆中的一个对象。)
4.JavaScript的原型系统:
-
如果所有对象都向私有字段[[prototype]],这个字段指向的就是对象的原型
-
读一个属性,如果对象本身没有,则会继续访问对象的原型,直到原型为空或者找到为止。
5.ES6提供的可以直接访问原型的方法:
-
Object.create(),参数是一个对象,为创建的对象的原型,可以为null。
-
Object.getPrototypeOf(),参数是一个对象,获得它的原型
-
Object.setPrototypeOf(obj,prototype),设置一个对象的原型
6.在早期的版本中方为一个对象的[[class]]的方法是Object.prototype.toString来访问,在ES5开始,[[class]]私有属性被Symbol.toStringTag来答题了,也就是说从ES5开始,Object.prototype.toString访问的是Symbol.toStringTag,而后者代表的是对象的类型,也就是说可以利用Object.prototype.toString来获得对象的类型。
所以
let o = {[Symbol.toStringTag]:"myobj"};
console.log(o+"");//[object myobject]
因为“+”运算符触发了o对象的类型转换,也就是o会转化为string基本类型,调用了toString方法,获得了此时类型的值。
7.new操作符具体干了些什么事情呢?
new运算符接受一个构造函数和一组调用参数:
-
以构造器的prototype属性为原型,创建新对象;
-
将this和调用参数传递给构造器,执行。
-
如果构造器返回的是一个对象,则返回,否则返回第一步创建的对象。
模拟Object.create的实现
let o = {
name:"krys"
};
Object.create = function (prototype){
let newp = function () {};//创建新对象
newp.prototype = prototype;//指定原型
return new newp;//返回这个对象
}
let b = Object.create(o);
console.log(b);
8.ES6引进了class关键字,并在标准中删除了所有的[[class]]相关的私有属性描述。
(二)JavaScript中的对象分类
对象主要可以 分成以下几类
-
宿主对象:由JavaScript宿主环境提供的对象,他们的行为完全由宿主环境决定
-
内置对象:由JavaScript语言提供的对象,其中包括
-
固有对象:由标准规定,随着JavaScript运行时创建而自动创建的对象实例
-
原生对象:由用户通过内置构造器或者特殊语法创建的对象
-
普通对象:由{}语法,Object构造器或者class关键字定义的对象,能被原型继承。
1、宿主对象
JavaScript的宿主对象千奇百怪,其中之一就是浏览器环境中的宿主。
在浏览器环境下,window就是全局对象,而这个浏览器的全局对象window的属性,有些是来自于JavaScript,有些是来自浏览器环境的。
2、固有对象
固有对象是有JavaScript的标准规定的,岁JavaScript运行时创建而自动创建的对象实例。固有对象在JavaScript代码执行前就已经被创建出来了,扮演着类似基础库的角色。
3、原生对象
-
能通过语言本身的构造器创建出来的对象叫做原生对象
-
所有这些构造器是无法用JavaScript代码实现的,也无法用class extends来继承的。
-
这些构造器创建的对象拥有一些特殊字段,而这些字段使得原型对象继承方法无法正常工作。
-
可以通过new运算符来创建对象
-
原生对象的类型如下
基本类型 |
基本功能和数据结构 |
错误类型 |
带类型的数组 |
Boolean String Number Object Symbol |
Array Date RegExp Promise Proxy Map Set WeakMap WeakSet |
Error EvalError ShareArrayBuffer DatoView |
Float32Array Float64Array Int8Array Int16Array Int32Array UInt8Array UInt16Array UInt32Array UInt8ClampedArray
|
4、用对象来模拟函数与构造器:函数对象与构造器对象
因为JavaScript为原生对象预留了私有字段机制,分别是[[call]],[[construct]]。
任何对象只要能实现上面两个字段,那么他就是一个函数或者是构造器。
用户用Function关键字创建的函数必定同时是构造器和函数,但是它们表现的行为效果并不相同。
ES6的箭头函数仅仅是函数,不能作为构造器使用。
construct的执行过程如下(用function创建的对象)
-
以Object.prototyope为原型创建一个对象
-
以新对象为this,执行函数的[[call]]
-
如果call的返回值是对象,那么返回这个对象,否则返回第一步的对象。
function myobj() {
name:"krys"
}
let oo = new myobj();
console.log(oo);
例如上面的代码,因为函数没有返回一个对象,所以返回的是Object的prototype创建的对象。
如果像下面这样返回一个对象的构造函数
function myobj() {
return {
name:"krys"
}
}
let oo = new myobj();
console.log(oo);
那么myobj里面的对象,永远没有办法被外部使用。也即是不能直接使用myobj里面的对象,只能像上面那样先获得一个对象,再使用这个对象的属性。
5、特殊行为的对象
在固有对象和原生对象中,有一些对象的行为与正常对象有很大的区别。
-
Array的length属性,会根据最大的下标自动发生变化
-
不能给Object.prototype指定原型,因为它已经是所有正常对象的默认原型了。
-
Arguments,arguments的非负整数下标属性跟对应的变量联动