关于构造函数和普通函数的小记

我们先来思考一下下面代码的输出,来引入今天的问题:

const a = new Foo()
console.log(a.id) // 1
const b = Foo() 
console.log(b.id) // 2
const c = new Foo()
console.log(c.id) // 3

id是函数对象Foo中的成员属性,每一次对Foo函数的调用,得到一个Foo对象的实例,打印id值,引出两个问题:

  1. 每个实例对象的id值每次调用都是递增的,是怎么实现的呢?
  2. new Foo()Foo()调用的区别是什么呢?

本着能动手就先动手实现的原则,后面再分析原理,我先写代码实现一波先:

第一版
let tmp = 1
function Foo() {
	if (this instanceof Foo) {
		this.id = tmp++
	} else {
		return new Foo()
	}
}

我们实现了上图展示的效果,但是这里用到了全局变量tmp,我们都知道全局变量会透着一股不好的味道,会引出意想不到的bug出来,所以才会出现诸如模块化机制来隔离变量作用域等;所以,我们打算第二版引入闭包来优化一下代码,如下所示:

第二版
let Foo = (function() {
  let tmp = 1
  return function() {
    if (this instanceof Foo) {
      this.id = tmp++
    } else {
      return new Foo()
    }
  }
})()

这里我们是通过 立即执行函数 的方式去实现的,tmp封装在立即执行函数的内部;现在,在Foo函数的外部是访问不到tmp变量的,并且返回一个函数作为返回值,里面的这个内部函数我们就叫做 闭包 了,闭包就可以访问到外部函数的tmp变量。

相信读者读到这里,关于第一个问题的解答应该是解释清楚了:本质就是通过闭包(内部函数)调用一个内部变量递增实现给id属性赋值。

接下来我们来解答一下第二个问题:new运算符加与不加有何区别?
直接引入MDN上面的解释吧:

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。new 关键字会进行如下的操作:
1.创建一个空的简单JavaScript对象(即{});
2.链接该对象(即设置该对象的构造函数)到另一个对象 ;
3.将步骤1新创建的对象作为this的上下文 ;
4.如果该函数没有返回对象,则返回this。

我们先来看一下下面这一段代码

	function Foo() {
		this.id = 1
    }
    const f = Foo()
    console.log(f.id) //  TypeError: Cannot read property 'id' of undefined

这里没有加 new 运算符, 仅仅是普通的 foo() 函数调用,this 的指向是根据运行时决定的,很明显 this目前所在的环境是 全局上下文对象 ,在浏览器环境下,foo()的调用就等价于 window.foo() 的调用,因此, this的指向就是 window本身啦,this.id = 1的操作本质上就相当于window.id = 1

console.log(window.id) // 1

说了一大堆,接下来正式进入主题,加上new运算符之后实际上就是改变了函数对象内部this的指向了,这时候this指向了该函数对象创建的实例本身了。

	function Foo() {
		this.id = 1
    }
    const f = new Foo()
    console.log(f.id) //  1

再返回来看一下刚开始的问题:

const b = Foo() 
console.log(b.id) // 2

这个 b实例看上去只是普通的函数对象调用,反而能够使用了内部的id属性,这不是很奇怪吗?我们再去看里面的具体函数实现:

let Foo = (function() {
  let tmp = 1
  return function() {
    if (this instanceof Foo) {
      this.id = tmp++
    } else {
      return new Foo()
    }
  }
})()

原来内部使用了 instanceof 运算符去判断了一下 当前的 this 是否为 Foo 创建的实例,否则会再次通过 new 去调用一次自身去获得一个实例对象,这样就能正确的使用到它的id 属性啦。

结论:js 里面,我们所说的 构造函数 无非其实就是在 普通函数 前面加上一个 new 运算符,以此来获得函数对象的属性,本质上还是this的指向问题。所以大家不要被迷惑了。

关于 new 的小结暂且告一段落,后期想到再做补充~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值