手写new
构造函数做了什么?
写之前,需要先了解下new做了什么?
- 由于new之后返回的是一个实例对象
- 该实例有隐式原型指向构造函数的显示原型
- 该实例对象有构造函数的所有属性
- 返回这个对象
// 前提,构造函数Fn
function Fn(){}
function myNew () {
const obj = {}
obj.__proto__ = Fn.prototype
const result = Fn.call(obj)
return result instanceof Object ? result : obj
}
return判断?
最后一步,为什么有判断?
构造函数内容扩展---- return
function Person (name, age) {
this.name = name
this.age = age
// 没写return ,相当于返回了undefied,默认返回了this
return '' //{ name: 'lucy', age: 18 }
return false //{ name: 'lucy', age: 18 }
return 1 //{ name: 'lucy', age: 18 } 基本数据类型 Number Boolean String Undefined Null
return {} // {}
return [] // []
return function print(){ console.log('print') } // function print(){ console.log('print') }
}
const p = new Person('lucy', 18)
我们通过以上5种,构造函数的return,发现:
- 构造函数,没有写return,其实实际我们理解为返回了this对象
- 字面看起来,没写return,相当于return undefied ,进一步我们发现,如果return 了基本数据类型,那么实例p没被影响,还是 { name: 'lucy', age: 18 }
- 如果return了引用类型,那么实例就是返回的引用类型的值
因此,myNew第三步骤执行完,获得的result保存的是构造函数返回return的值,而不同的类型将会影响构造函数的作用。
因此,当 res 为引用数据类型时 应返回 res 本身,而当 res 为基本数据类型时 应返回创建的新对象。
手写new基础实现
// 前提,构造函数Fn
function Fn(){}
function myNew (Fn) {
const obj = {}
obj.__proto__ = Fn.prototype
const result = Fn.call(obj)
return result instanceof Object ? result : obj
}
手写new函数的进阶(优化和变动)
了解了构造函数的基础操作之后,我们可以通过以往知识,减少代码行数。
Object.create
Object.create(base) 创建一个基于base的新对象,且base是新对象的原型
const base = {}
const newObj = Object.create(base)
newObj.__proto__ === base // true
因此,对于基础写法的第一二行,可以合并
function myNew(Fn){
// const obj = {}
// obj.__proto__ = Fn.prototype
const obj = Object.create(Fn.prototype)
const result = Fn.call(obj)
return result instanceof Object ? result : obj
}
call, apply, bind
其实这里第三步除了用 apply 绑定this并执行构造函数中的语句,还可以用另外两种显示绑定。
function myNew(Fn,...args){
// const obj = {}
// obj.__proto__ = Fn.prototype
const obj = Object.create(Fn.prototype)
const result = Fn.call(obj, )
return result instanceof Object ? result : obj
}
let res = fn.call(obj, ...args)
// 使用 call 时,参数是逐个传参,因此要使用数组的解构
let res = fn.bind(obj, ...args)()
// 使用 bind 时,参数也是逐个传参,因此也要使用数组的解构
// 此外, bind 是不会自行执行的,必须手动调用
3. 使用 typeof 判断对象实例类型
有些文章里最后一步可能会使用 typeof 来判断类型,但我们必须注意 typeof null === "object" 这个bug的存在,以及 typeof 函数 === “function” 这种情况。
因此不能简单的写作下面这种方式:
function myNew(fn, ...args) {
let obj = Object.create(fn.prototype);
let res = fn.apply(obj, args)
console.log(res,typeof res)
return (typeof res === "object" && res !== null) || (typeof res === "function") ? res : obj // 直接使用 typeof 来判断
}
当构造函数的 return 值为 null 时,控制台打印实例为 null 。
手写instanceof
语法
obj instanceof Constructor. //true/false
作用
左侧是右侧的实例吗 =》 右侧构造函数的原型对象在左侧对象的原型上吗=》 Constructor.prototype === obj.proto
思路
- 基本数据类型,instanceof 都是false;null 也是基本数据类型
- 左侧对象的原型对象是null,返回false
- 左侧隐式原型对象是右侧构造函数的显示原型,返回true
- 如果前两者都不是,持续顺着左侧原型的原型上走
function myInstaceof(left, right){
if (type left !== 'object') return false
if (left === null) return false
let proto = Object.getPrototypeOf(left)
while(true){
if (proto === null) return false
if (proto === right.prototype) return true
proto = Object.getPrptotypeOf(proto)
}
}
通用的获取数据类型的方法
function getType (obj) {
// 基本数据类型,直接获取typeof
if (typeof obj !== 'object') {
return typeof obj
}
// 非基本数据类型,通过ES6方式获取 '[object Array]'截取
return Object.prototype.toString.call(obj).slice(8, -1)
}