单例模式:为什么我只能new一次啊???new 的过程发生了什么 在js中秉承着一切皆对象的概念,es5里面通常是用首字
在传统的面向对象过程中,我们都知道
new
关键字是用来构造一个新对象的;同样的,在js
中,new
关键字也可以构造出一个新对象,但是你知道js
中new
过程都干了些什么吗?今天就让我们一起探讨一下new
一个对象的过程以及编程模式中的单例模式。

new 的过程发生了什么
在js
中秉承着一切皆对象的概念,es5
里面通常是用首字母大写的函数(也是对象)来代表类的构造函数;通过new Duck()
的方式,就可以构造出一个新的对象。

原型式面向对象
但是,js
中不同于传统的面向对象,它是原型式的面向对象,我们在给类添加方法时,只能往类的构造函数身上的原型上添加方法,这样new出来的实例对象才能访问到。

在原型之中还存在着一条规则:类构造函数的显示原型等于它实例对象的隐式原型,即yw.__ proto__ = Duck.prototype
。

this 指向
在类的构造函数中,我们可以看到内部还有this
关键字,关于它的用法我们可以先看下面一段代码;

当对象字面量中的函数方法被以对象的方法调用时,显然这时的this
就会指向这个对象。然而;

当我们把对象中的方法拿出来以普通函数的方式运行,此时的this
指向的就是全局。
所以,在函数里面,this
的指向是由它的运行方式来决定的;当函数以对象的方法调用,此时的this
是动态的,指向调用的那个对象;而把函数用最普通的函数方式来运行时,this
就会指向全局(window)。
那有没有什么方法可以改变关键字this的指向呢?当然有,call,apply,bind
等方法都能改变this
的指向,在这里我们就以apply
为例;

上面的代码中,我们就用apply
方法把this
的指向绑定到了对象obj
上,输出obj.name
。
隐式原型
在new
实例化对象后,我们知道实例化的对象都是可以访问到类身上的属性和方法的;但是在js
中,我们是用函数对象来表示一个类的,我们通常把实例对象要访问的方法都写在构造函数的原型上;那在new
过程中,我们就要有这样一个步骤,让新实例化的对象的隐式原型能够等同于构造函数的显示原型。

在上面的代码片段中,其中obj3.__ proto__
就叫做obj3
的隐式原型,我们把对象obj2
赋给了obj3
的隐式原型,也就是说虽然对象obj3
中没有任何属性,但是由于原型,打印obj3.name
时,还是能访问到其原型obj2
上的属性name
。

手写 new
结合以上的内容,其实我们大概可以知道,new过程到底干了些什么?
- 先创建一个空对象 {}
- 让 this 指向实例对象,通过构造函数往实例中逐渐添加属性
- 再把 实例对象的隐式原型__proto__ = 构造函数(Duck) 的显示原型prototype
- 返回实例对象
手写new过程如下:
function myNew(Fun,...args) {
let obj = {}; // 对象,内存在堆内存中,地址引用
Fun.apply(obj, args); // 改变this指向
obj.__proto__ = Fun.prototype; // 继承 原型对象
return obj;
}
利用我们手写的myNew函数也能够实例化对象;


单例模式
有时候,我们在实例化对象时,只需要实例化一次就够了,并不需要多次实例化新对象出来,比如在很多网站中,99%的是用不到注册的,登录注册弹窗不需要每次实例化的,这样网站会更快;这时候就要引入我们单例模式的应用了。那何为单例模式呢?
单例模式(Singleton Pattern)是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。这种模式通常用于那些需要频繁创建和销毁、开销大或者必须共享单一资源的情况,比如线程池、缓存、日志对象、对话框、注册表设置等。
new 会创建新对象
先来看下面一段代码;

我们用new
关键字创建了两个新对象,obj1
明显不会等于 obj2
,因为new
过程是会为新对象开辟新的内存空间的,这两个显然不是同一个对象。

那要实现单例模式,我们显然不能使用new去构造我们的实例对象;那要怎么实现呢?
静态方法 getInstance
我们可以通过静态方法getInstance
和静态属性去实现;
静态方法是一种特殊的类成员方法,它并不与类的任何特定实例绑定,而是与类本身绑定。这意味着静态方法可以在不创建类的实例的情况下被调用。静态方法通常用于执行与类相关但不依赖于任何特定实例状态的操作。
// 单例模式 有的类只实例化一次,性能比较好
var Singleton = function (name) {
this.name = name
}
Singleton.prototype.getName = function () {
console.log(this.name);
}
// 静态方法 属于类的
Singleton.getInstance = function (name) {
if (!this.instance){
// 静态属性
this.instance = new Singleton(name)
}
return this.instance
}
var s1 = Singleton.getInstance('kunkun')
var s2 = Singleton.getInstance('kunfc')
console.log(s1 === s2, s1.name,s2.name); // true 单例模式保证只有一个实例
在上面的代码中,我们通过静态方法getInstance
,在每一次创建对象实例时,先判断静态属性instance
是否存在,如果不存在,则创建新的对象并赋给静态属性instance
,存在则直接返回instance
。这样,我们就能确保每次只能实例化一次了。

小结
js
有着强大的表现力;可以通过直接创建对象字面量来新建对象;但是有一些对象,有着共同的行为(如果每个对象字面量都有自己的函数,内存开销太大),创建类的性能比较好;而每个对象肯定要有自己的属性,而且是不一样的,这时候构造函数constructor
就可以通过this
帮我们构造不同的属性值;函数方法则放到prototype
原型对象上,就能被所有实例共享。
转载自:https://juejin.cn/post/7389913889331380276