构造函数及实例化原理/包装类

105 阅读5分钟

📌 构造函数及实例化原理

new的实现原理

1、创建新对象

2、将该对象的原型与构造函数挂钩

3、通过call或apply改变构造函数this指向该新对象,并且将参数传递给该新对象

4、判断构造函数是否有返回对象或函数,无则返回创建的该对象

//模拟 new 关键字实现功能
function _new(/* 构造函数 */constructFunction, /* 构造函数参数 */...args) {
        //步骤1: 创建一个新的空对象
        let obj = {};
        //步骤2: 将该对象的 __proto__ 与构造函数constructFunction的 prototype 进行挂钩
        obj.__proto__ = constructFunction.prototype;
  			//步骤3: 改变构造函数constructFunction的this指向该空对象,传入参数并执行
        const res = constructFunction.call(obj, ...args)
        //步骤4: 判断构造函数是否有返回对象或函数,有则返回,没有则返回创建的这个新对象
        return res instanceof Object ? res : obj
}

//定义一个构造函数Preson
function Preson(opt) {
      this.name = opt.name
      this.age = opt.age
}
//给构造函数Preson的原型prototype添加sayName方法
Preson.prototype.sayName = function () {
      console.log('我是:' + this.name +'\n'+'今年:' + this.age);
}

//定义一个对象,作为实参
var myInfo = {
      name: '张三',
      age: 16
}

//将构造函数Preson以及构造函数的实参myInfo作为_new函数的实际参数传入
//得到一个通过_new函数return的实例对象
var _newPreson = _new(Preson, myInfo)
//调用该实例对象原型链上的sayName()方法
_newPreson.sayName()
//结果: 我是:张三  今年:16

原理解析

  • _new函数内部通过字面量方式创建新的空对象obj
  • 将这个空对象obj的原型__ proto__属性指向****构造函数constructFunction的**prototype属性(空对象继承了构造函数原型的所有属性
  • 通过call方法改变构造函数constructFunction的this指向到这个新的空对象 objobj就可以访问到构造函数中的属性
  • 判断构造函数constructFunction是否有返回的对象或函数没有返回这个空对象obj

一句话概括: 创建新对象,修改原型链指向构造函数和把构造函数this指向新对象并返回

为何不能给 构造函数 显式(手动)添加return返回值?

构造函数中 return 基本类型生成的实例都会返回一个对象

//直接 return  -----------------------------------------------
function A(){  
   this.a = 1
   return;  
}  

var a = new A() // A {}  

//返回 数字类型  -----------------------------------------------  
function B(){  
   return 123;  
}  

var b = new B() // B {}  

//返回 string类型  -----------------------------------------------  
function C(){  
   this.a = 3
   return "abcdef";  
}  

var c = new C() // C {}  

//返回 数组  -----------------------------------------------  
function D(){  
   return ["aaa", "bbb"];  
}  

var d = new D() // ["aaa", "bbb"]  

//返回 对象  -----------------------------------------------  
function E(){  
   this.a = 5
   return {a: 2};  
}  

var e = new E() // Object {a: 2}  

//返回 包装类型   ----------------------------------------------- 
function F(){  
   this.a = 6
   return new Number(123);  
}  

var f = new F() // Number {[[PrimitiveValue]]: 123} 

一句话概括: 使用new关键字只能返回一个对象,要么是实例对象,要么是return语句指定的对象

  • 如果显式return基础类型(null, undefined, Boolean, String, Number, Symbol ),对构造函数无影响,依然返回新创建实例对象
  • 如果显式return引用类型(Object, Array, function),实例对象就会返回该引用类型值直接切断了和构造函数的联系

📌 包装类

JavaScript数据类型

  • 基本类型:Undefined, Null, Boolean, Number, String
  • 引用类型:Object, Array, Date, RegExp (其实就是对象)

原始值(基本类型)不能添加属性和方法,只有对象才有 属性和方法

当给原始值添加属性,系统会自动进行包装类,其只存在于一行代码执行的瞬间(隐式中间环节即包装类过程)

包装类处理步骤:

  • 通过new创建一个基本数据类型的实例对象
  • 调用该实例对象指定的方法
  • 销毁这个实例
var str = 'Hello javascript!';
str.nickName = 'JS';
console.log(str.nickName); //undefined

JS隐式的包装类步骤:----------------------------
var str = 'Hello javascript!';
//步骤一:隐式将原始类型字符串转为包装实例对象
new String(str).nickName  = 'JS';
//步骤二:自动创建的基本包装类型的对象只存在于一行代码执行的瞬间,后被立即销毁delete

//执行打印前new String(str).nickName  = 'JS'已被销毁
console.log(str.nickName); //undefined

字符串[string]原始值拥有length属性的原因

var str = 'abcdefghigklmn'

//str.length
//JS隐式的包装类步骤:----------------------------
new String(str).length

将字符串隐式经过包装为实例对象,这个实例对象中拥有内置的length属性
new String(str)包装后的数据是一个伪数组(),拥有length属性
  • 【如何判断是不是伪数组】

    • 首先他是个对象
    • 是对象,有length属性
    • 有length,值必须是number类型
    • length值是number类型,并且值不为0,这个对象还得按照下标存储数据
  • 【如何判断是不是真数组】

    • 数据 instanceof Array
    • Object.prototype.toString.call( 数据 ) === '[object Array]'
  • 【伪数组转为真数组】

    • 方法一:Array.prototype.slice.call( 数据 )
    • 方法二:声明一个空数组,通过遍历伪数组把它们重新添加到新的数组中

基本包装类型(包装对象)

  • 为方便操作基本类型值,ECMAScript提供了三种特殊的引用类型
    • String()可以将基本数据类型String转换为String对象
    • Number()可以将基本数据Number类型转换为Number对象
    • Boolean()可以将基本数据类Boolean型转换为Boolean对象
  • 基本数据类型 undefined 和 null 不能有属性和方法

基本包装类型与引用类型的区别?

主要区别:对象的生存期

  • 引用类型 :new关键字创建的引用类型的实例,执行流离开当前作用域之前一直保存在内存中
  • 基本包装类型 只存在一行代码的执行瞬间,然后立即销毁(不能再运行时添加属性及方法的根本原因)