JavaScript 自定义对象
及
new() 原理与实现
object
表示类型,是 JavaScript 的六种 主要类型 之一(ES6后新增了Symbol为7种) ,也就是我们所说的 “对象” ,用于存储各种键值集合和更复杂的实体。Object()
(首字母大写)是JavaScript语言中提供的用于创建object
的构造器,相当于一些语言中的Object
类,也就是说在 JavaScript 中所有的对象都是Object
"类"的实例。使用
new
是运算符用于创建对象的方式之一。
1. JavaScript中自定义对象的方法
1.1 直接创建
在一对花括号中填写若干个属性名和属性值的方法可以用于直接创建对象,这是最简单也最直观的方法,其格式为:
var objName = { attribName1: attribvalue1, attribName2: attribvalue2, ... }
例如:
var student = { name: '李华', age: 12, sex: 'male' }
1.2 通过 Object 对象创建
Object
是JavaScript内置对象构造器,
obj = new Object([value])
例如:
var student = new Object(); student.name = '李华'; student.age = 12; student.sex = 'male';
这与 1.1 节例子中创建的对象是一样的。
1.3 通过自定义构造函数创建
以下这种方式只能在非严格模式下成立:
// 1. 定义作为`构造函数`的函数 function Teacher(name, sex, height){ this.name = name; this.sex = sex; this.height = height; } // 创建对象实例 var teacher1 = new Teacher("ZhangSan", "male", 190);
1.4 关于 this 的指向的说明
1. 在全局执行环境中(在任何函数体外部)this 都指向全局对象。在浏览器中, window 对象
同时也是全局对象。
【注意】在严格模式下将有所不同,函数内部的
this
会若无赋值,将一直保持为undefined
!
2. 在函数内部,this的值取决于函数被调用的方式。
在1.3 节中,如果直接调用Teacher
函数即执行("ZhangSan", "male", 190)
,this
将指向window
对象(非严格模式)。然而将其作为构造器使用时,即执行new Teacher("ZhangSan", "male", 190)
时,new
运算符改变了this
的指向,使其不再指向window,而是指向新创建的对象。这在之后的自己实现 new章节的代码中将会看到具体的过程。
2. new
运算符在创建自定义对象的过程
new
运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。具体来说,它做了以下工作:
- (1)创建一个空的简单JavaScript对象(即
{}
); - (2)为步骤1新创建的对象添加属性
__proto__
,将该属性链接至构造函数的原型对象 ; - (3)将(1)中新创建的对象作为
this
的上下文 ; - (4)如果该函数没有返回对象,则返回
this
。
Object.prototype.__proto__
已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。通过现代浏览器的操作属性的便利性,可以改变一个对象的 [[Prototype]] 属性, 这种行为在每一个JavaScript引擎和浏览器中都是一个非常慢且影响性能的操作,使用这种方式来改变和继承属性是对性能影响非常严重的,并且性能消耗的时间也不是简单的花费在 obj.proto = … 语句上, 它还会影响到所有继承来自该 [[Prototype]] 的对象,如果你关心性能,你就不应该在一个对象中修改它的 [[Prototype]]。
3. 自己实现一个拥有new
功能的函数
3.1 完整手写实现
为了完整体现new
的过程,这里禁止调用Object.create()
和Function.prototype.apply()
这两个方法,完整地按照2 小节中的步骤进行实现。
function myNew(cstrct,...params){ // 1.创建一个空的JavaScript对象 obj const obj = {}; // 2.为新创建的对象(obj)添加属性`__proto__`, // 并将该属性 链接至 构造函数的 原型对象 ; obj.__proto__ = cstrct.prototype; // 3. 将该新对象(obj)作为this的上下文 ; // 3.1 在 obj.__proto__ 上临时挂上构造函数 obj.__proto__._func = cstrct; // 3.2 执行该构造函数得到 "this" let _ = obj._func(...params); // 3.3 删除该临时挂载的属性 _func(否则实例上会多出这个属性) delete obj.__proto__._func // 4. 如果该函数没有返回对象,即null或undefined, // 则返回this,否则返回该对象 return _ instanceof Object ? _ : obj; }
3.2 调用现成方法实现
了解了完整地实现过程之后,再介绍两个方法:
方法 | 语法 | 描述 |
Object.create() |
Object.create(proto,[propertiesObject]) |
使用指定的原型对象和属性创建一个新对象。 |
Function.prototype.apply() |
func.apply(thisArg, [argsArray]) |
用于调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。 返回 调用有指定 this 值和参数的函数的结果。 |
因此3.1 节中的
function myNew(cstrct,...params){ const obj = Object.create(cstrct.prototype) obj.__proto__._func = cstrct; // 3.2 执行该构造函数得到 let _ = obj._func(...params); // 删除该临时挂载的属性 _func(否则实例上会多出这个属性) delete obj.__proto__._func return _ instanceof Object ? _ : obj; }
4. 使用 new 创建 类(class) 实例
首先很遗憾地说,JavaScript 并不是基于 类 的面向对象编程语言,即使在有了class
关键字后,本质上还是基于原型链的,而class
只是为了模仿其它语言中的类而新增的语法糖。ES6引入了类
也就是class
,其本质上是一个JavaScript 函数
。
例如:
class Car{ public color; public height; constructor(color,height){ this.color = color; this.height = height; } run(){ console.log('I am running at high speed...'); } serfIntroduce(){ console.log(`I am a ${this.color} car with a height of ${this.height.toString()} meters.`) } } const lamborghini = new Car('green',1.05)
实际上上面的代码只是以下代码使用class
语法糖的结果:
var Car = (function () { function Car(color, height) { this.color = color; this.height = height; } Car.prototype.run = function () { console.log('I am running at high speed...'); }; Car.prototype.serfIntroduce = function () { console.log("I am a ".concat(this.color, " car with a height of ").concat(this.height.toString(), " meters.")); }; return Car; }()); var lamborghini = new Car('green', 1.05); lamborghini.serfIntroduce(); lamborghini.run();
从以上代码可以看出虽然自从ES6标准发布后在形式上看JavaScript中的"类"看起来就像 Java 等语言中的类用法一样,但本质上JavaScript中的new
只是模拟了Java等语言中new
的行为。