重学JS(3)- 构造函数,来看看我的表述

313 阅读6分钟

构造函数

回归题目,构造函数.

要理解构造函数,就要明白js中对象的定义.

构造函数的作用就是为了复制对象.

在tc39中对于constructor(构造器)的描述是「function object that creates and initializes objects」(创建和初始化对象的函数对象.

如果是基于工作中的理解,就是一个模版,目的就是为了按照这个模版创造对象.

1. 基本使用

function Teacher({name, age, skill}) {
  this.name = name
  this.age = age
  this.profession = '教师'
  this.skill = function() {
    return skill
  }
}

const 班主任 = new Teacher({name: '蚂蚱', age: 28, skill: '吃馒头'})
const 数学老师 = new Teacher({name: '二狗子', age: 55, skill: '数学'})

一个模版,很方便就创造传来了两个对象,这个两个对象有一个共同的profession.

2. 为什么要使用构造函数

要理解这一点,我们首先要清楚对于JavaScript面向对象的思考这一文中第七章的例子.

使用构造函数就是为了封装对象的模版,让代码更加容易实现高内聚.

先来看一个例子:

function Test() {
  let n = 10;
  return function() {
    n += 11
    console.log(n)
  }
}

let a = Test()
a() // 21

let b = Test()
b() // 21
b() // 32

a() // 32

👆 代码创建了 一个函数,并实现了闭包.然后在分别给两个变量给定义了. 这个两个变量的值互不干扰. 但是又复制了相似的作用域.

但是函数按理说也是对象的一种,赋值的话不应该是引用类型么?

但是这个例子告诉我们,它赋值的过程是开辟一个新的内存来对作用域进行存储的.

new就是改变函数内部的this指向.从OG中指向window改为你实例化的对象.

function Text({
  color, brand
}) {
  var me = {}
  me.color = color
  me.brand = brand

  return me
}


const a = Text({color: '红色', brand: '玛萨拉蒂'})
const b = Text({color: '蓝色', brand: '法拉利'})

console.log(a)
console.log(b)

new的过程就是手动给你创建了一个return的值

function test2() {
  this.name = '妈咋'
  return this
}

const c = test2()
c.name = '哈哈'
console.log(c) // 哈哈

const d = test2() 
console.log(d) // 妈咋

const e =  new test2()
console.log(e) // 妈咋

上面这个例子更加验证了这个说法.new 的过程就是return 一个this回去.

3. 包装类

一个变量实例化之后,就是一个对象,就能够使用对象的那些东西.

包装类一般来说是宿主环境下自带的类(构造函数).

比如说new Number() 、 new String() 、new Boolean() 等.

首先明确一点的是,js的基本类型是没有属性的.但是

const a = '123'
console.log(a.length)

a理论应该是被基本类型赋值的吧,为何又会存在length 这个属性么? 而且点语法不是引用类型才存在的么?

这怎么和一开始说好不不一样,你怎么和女人一样说变就变!

其实,js并没有变,只是它在你运行代码的是,给包装了一层方法, 然后你就看到了这样的效果.

过程是这样的:

  1. String类型赋值之后,你还要.length , JS看到是String,这玩意儿没有是基本类型没有属性. 但是无奈你非要.
  2. a.length 过程中,JS引擎创建一个new String() 抱住a
  3. new 就是让a变成一个对象. 这时候你逗号它,最多会报undefind
  4. 宿主环境内置的new String() 构造函数上面自带.length 就能够给你返回它的长度了

length 是new String() 内置的属性

如此一来,一切都解释得通了.

基本类型没有属性, 只能通过一定的方法,把它改成对象,才能够.length.

\

既然已经将a的LHS改为了一个对象

const a = '123'
a.len = 3
console.log(a.len) // undefind

你刚刚还说是a会自动转化为对象的?怎么加一个len就不可以了呢?

实际过程如下

const a = '123'
a.len = 3
(
  new String(a).len = 3
  delete new String(a).len
)
console.log(a.len) // undefind

括号中是JS背地里做的事情. 跟着delete 是因为,虽然给你赋值成功了, 但是这个变量没有存储的地址,所以JS 引擎中的OC系统会开动,把这种游离的,不被使用的内存给清楚掉.

值得一说的是,如果a.length 的话, 得到的结果会是3 ,是由于在解析a.length 的时候又重复new String(a).length .

如果我们非要在a上面自建属性的话:

const a = new String('123asd')
a.len = 33
console.log(a.len) // 3

会出现如下的变形:

let a = new String('123')
a.length = 2
a.len = 44
a.len = 66
console.log(a) // [String: '123'] { len: 66 }
console.log(a.length) // 3

内部属性不可更改? length 没有变化

4. 深入了解构造函数这一名称

要明确一点,在JavaScript中函数就是函数,函数也是基于对象,Functino.prototype => Object.prototype. 所以说,在JS中函数有一等公民的称呼.简单来说,函数没有 ‘构造’函数这么一个函数类型.

构造函数之所以叫做构造函数,是因为它new 了之后才叫做构造函数. 是一种函数调用的类型.

所以准确的说,应该叫做 - 构造调用函数. 它和普通函数调用的方式本质也没有区别,只是在这过程中封装了一些属性.这些属性就构成了原型和原型链这一知识体系.

补充

cosnt obj = {
  name: '蟑螂'
}

const obj2 = new Object()
obj2.name = '蟑螂'

第一个叫对象字面量,第二个原生对象构造函数.

在html的script中调试的疑惑

function test() {
  this.name = 3
}
console.log(name) // 3

这里还能够理解,test函数的OG[[scope]]中this就是等于全局变量的.

function test() {
  this.name = 3
  this.smoke = function() {}
}
console.log(name) // 3
console.log(smoke)

这里就有点不理解了. 会报错

Uncaught ReferenceError: smoke is not defined

函数就不能挂载到全局变量当中了么? 不应该啊.

还有更加奇怪的

function test() {
  this.smoke = function() {
    console.log(1111)
  }
}
test()
smoke() // 1111

???发生了什么?为啥这样子全局就存在smke函数了呢?

太难了,我要转行!

百度一下,居然半天没有找到答案,甚至有发现了一点蹊跷的地方.

上面的例子是我在浏览器宿主下进行的,通过liveServe来保活.但是当我在node环境中输入的时候,异常却又合理的现象发生了 .

node test.js
function test() {
  this.name = 3
  this.smoke = function() {}
}
console.log(name) // 报错
console.log(smoke) // 报错

如此报错了,才是符合心中对于作用域的概念,舒服了

function test() {
  this.name = 3
  this.smoke = function() {}
}
test()
console.log(name) // 33
console.log(smoke) // Function

test()的运行,把test的oa作用域暴露了出来,给全局this捕捉到了,所以能够直接输出来.没毛病.

挂载在windows上面的属性没有被销毁,所以我在第一次跑了test()函数之后,再删除掉的话,结果没有任何变化,依旧能够打印出name和smoke

windows这个作用域在调试的过程中没有进行销毁