📌 构造函数及实例化原理
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 ? rel : 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指向到这个新的空对象obj(obj就可以访问到构造函数中的属性)
- 判断构造函数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创建一个基本数据类型的实例对象
- 调用该实例对象指定的方法
- 销毁这个实例
```javascript 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属性的原因**
```javascript
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关键字创建的引用类型的实例,执行流离开当前作用域之前一直保存在内存中**
- 基本包装类型**:只存在一行代码的执行瞬间,然后立即销毁**(不能再运行时添加属性及方法的根本原因)