JS中new操作手写函数的实现是怎样的
Admin 2022-08-13 群英技术资讯 960 次浏览
这篇文章主要讲解了“JS中new操作手写函数的实现是怎样的”,文中的讲解内容简单、清晰、详细,对大家学习或是工作可能会有一定的帮助,希望大家阅读完这篇文章能有所收获。下面就请大家跟着小编的思路一起来学习一下吧。ES6 之前(ES5),JavaScript 中类的表现形式就是构造函数。JavaScript 中的构造函数是怎么样的?
如果一个函数被 new 操作符调用了,那么它会执行如下操作:
[[Prototype]] 属性赋值为构造函数的 prototype 属性;this 赋值为这个新对象(即 this 指向新对象);下面,我们就根据上面的原理,尝试自己手写一个函数,模拟实现 new 操作符的功能。
我们先用 ES5 的语法来进行实现:
function useNewOperator() {
var constructor = arguments[0]
var args = [].slice.call(arguments, 1)
// 1. 在内存中创建一个新对象
var obj = {}
// 2. 这个新对象内部的 [[Prototype]] 指针被赋值为构造函数(constructor)的 prototype 属性
obj.__proto__ = constructor.prototype
// 3. 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)
// 4. 执行构造函数内部的代码(给新对象添加属性)
var res = constructor.apply(obj, args)
// 5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚才创建的新对象
if (res != null && (typeof res === 'object' || typeof res === 'function')) {
return res
}
return obj
}
// 测试
function Person(name, age) {
this.name = name
this.age = age
// return undefined
// return null
// return {}
// return 123
// return ''
// return String(123)
// return new String(123)
// return { name: 'wy' }
}
Person.prototype.sayName = function() {
console.log(this.name);
}
const p1 = new Person('zhj', 20)
console.log(p1); // Person {name: 'zhj', age: 20}
p1.sayName() // zhj
const p2 = useNewOperator(Person, 'zhj', 20)
console.log(p2); // Person {name: 'zhj', age: 20}
p2.sayName() // zhj
上面的基本实现能跑但还存在问题,即没有考虑传入第一个参数是否为函数类型,如果第一个参数传入的不是函数,那么在执行 constructor.apply(obj, args) 这行代码调用 constructor() 时就会报错了。所以我们需要加上判断,如果第一个参数传入的不是一个函数,就直接抛出异常:
function useNewOperator() {
var constructor = arguments[0]
+
+ if (typeof constructor !== 'function') {
+ throw new TypeError('the first argument to useNewOperator function must be a function')
+ }
+
var args = [].slice.call(arguments, 1)
// 1. 在内存中创建一个新对象
var obj = {}
// 2. 这个新对象内部的 [[Prototype]] 指针被赋值为构造函数(constructor)的 prototype 属性
obj.__proto__ = constructor.prototype
// 3. 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)
// 4. 执行构造函数内部的代码(给新对象添加属性)
var res = constructor.apply(obj, args)
// 5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚才创建的新对象
if (res != null && (typeof res === 'object' || typeof res === 'function')) {
return res
}
return obj
}
// 测试
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayName = function() {
console.log(this.name);
}
const obj = {}
const p2 = useNewOperator(obj, 'zhj', 20) // Uncaught TypeError: the first argument to useNewOperator function must be a function
console.log(p2);
p2.sayName()
前面我们在将新对象内部的 [[Prototype]] 属性赋值为构造函数的 prototype 属性时,是通过给 obj 上的 __proto__ 属性赋值实现的(相当于使用了 Object.prototype.__proto__),虽然可以,但不推荐使用 Object.prototype.__proto__,更推荐使用 Object.getPrototypeOf/Reflect.getPrototypeOf 和 Object.setPrototypeOf/Reflect.setPrototypeOf(参考链接:developer.mozilla.org/zh-CN/docs/… )。所以我们做如下修改:
function useNewOperator() {
var constructor = arguments[0]
if (typeof constructor !== 'function') {
throw new TypeError('the first argument to useNewOperator function must be a function')
}
var args = [].slice.call(arguments, 1)
// 1. 在内存中创建一个新对象
var obj = {}
// 2. 这个新对象内部的 [[Prototype]] 指针被赋值为构造函数(constructor)的 prototype 属性
+ Object.setPrototypeOf(obj, constructor.prototype)
// 3. 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)
// 4. 执行构造函数内部的代码(给新对象添加属性)
var res = constructor.apply(obj, args)
// 5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚才创建的新对象
if (res != null && (typeof res === 'object' || typeof res === 'function')) {
return res
}
return obj
}
// 测试
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayName = function() {
console.log(this.name);
}
const p1 = new Person('zhj', 20)
console.log(p1); // Person {name: 'zhj', age: 20}
p1.sayName() // zhj
const p2 = useNewOperator(Person, 'zhj', 20)
console.log(p2); // Person {name: 'zhj', age: 20}
p2.sayName() // zhj
或者我们还可以使用 Object.create() 直接指定原型来创建新对象:
function useNewOperator() {
var constructor = arguments[0]
if (typeof constructor !== 'function') {
throw new TypeError('the first argument to useNewOperator function must be a function')
}
var args = [].slice.call(arguments, 1)
// 1. 在内存中创建一个新对象
- var obj = {}
-
// 2. 这个新对象内部的 [[Prototype]] 指针被赋值为构造函数(constructor)的 prototype 属性
+ var obj = Object.create(constructor.prototype)
// 3. 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)
// 4. 执行构造函数内部的代码(给新对象添加属性)
var res = constructor.apply(obj, args)
// 5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚才创建的新对象
if (res != null && (typeof res === 'object' || typeof res === 'function')) {
return res
}
return obj
}
// 测试
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayName = function() {
console.log(this.name);
}
const p1 = new Person('zhj', 20)
console.log(p1); // Person {name: 'zhj', age: 20}
p1.sayName() // zhj
const p2 = useNewOperator(Person, 'zhj', 20)
console.log(p2); // Person {name: 'zhj', age: 20}
p2.sayName() // zhj
下面,我们再来使用 ES6 语法(剩余参数(rest parameters)、const)进行实现:
function useNewOperator(constructor, ...args) {
if (typeof constructor !== 'function') {
throw new TypeError('the first argument to useNewOperator function must be a function')
}
// 1. 在内存中创建一个新对象
const obj = {}
// 2. 这个新对象内部的 [[Prototype]] 指针被赋值为构造函数(constructor)的 prototype 属性
Object.setPrototypeOf(obj, constructor.prototype)
// 或者使用 Object.create() 直接指定原型创建新对象
// const obj = Object.create(constructor.prototype)
// 3. 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)
// 4. 执行构造函数内部的代码(给新对象添加属性)
const res = constructor.apply(obj, args)
// 5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚才创建的新对象
if (res != null && (typeof res === 'object' || typeof res === 'function')) {
return res
}
return obj
}
// 测试
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayName = function() {
console.log(this.name);
}
const p1 = new Person('zhj', 20)
console.log(p1); // Person {name: 'zhj', age: 20}
p1.sayName() // zhj
const p2 = useNewOperator(Person, 'zhj', 20)
console.log(p2); // Person {name: 'zhj', age: 20}
p2.sayName() // zhj
最后,还有一个点需要考虑,就是 ES6 新增的 new.target 属性,在通过使用 new 操作符被调用的构造方法或函数中,new.target 会返回一个指向构造方法或函数的引用(参考链接:developer.mozilla.org/en-US/docs/… )。所以我们可以使用 new.target 来检测函数或构造方法是否是通过 new 操作符被调用的。那么我们还需要在自己实现的 useNewOperator 函数中添加相应的代码:
function useNewOperator(constructor, ...args) {
if (typeof constructor !== 'function') {
throw new TypeError('the first argument to useNewOperator function must be a function')
}
useNewOperator.target = constructor
const obj = {}
Object.setPrototypeOf(obj, constructor.prototype)
// const obj = Object.create(constructor.prototype)
const res = constructor.apply(obj, args)
if (res != null && (typeof res === 'object' || typeof res === 'function')) {
return res
}
return obj
}
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
匹配对象。如果有省略号,对象可以有更多的属性。只检测自己的属性(Object.keys),忽略原型中的属性。对象语法支持特殊识别属性,快速属性,属性不支持尾逗号。
在本篇文章里小编给大家整理的是一篇关于js定时器出现第一次延迟的原因及解决方法,对此有需要的朋友们可以学习下。
这篇文章主要讲解前端的状态管理,状态管理李娜就想到:Vuex、Redux、Flux、Mobx等等方案,不论哪种方案只要内容一多起来似乎都是令人头疼的问题,今天来聊一聊前端的状态管理,感兴趣的小伙伴可以参考参考下面文字具体内容
高大上先上部署node方式:直接通过nodeapp来启动,如果报错了可能直接停在整个运行,supervisor感觉只是拿来用作开发环境的。目前似乎最常见的线上部署nodejs项目的有forever,pm2这两种。使用场合: supervisor是开发环境用。forever管理多个站点,每个站点访问量不大,不需要监控。pm2网站访问量比较大,需要完整的监控界面 pm2主要
这篇文章主要为大家详细介绍了jQuery呼吸轮播图制作原理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
成为群英会员,开启智能安全云计算之旅
立即注册关注或联系群英网络
7x24小时售前:400-678-4567
7x24小时售后:0668-2555666
24小时QQ客服
群英微信公众号
CNNIC域名投诉举报处理平台
服务电话:010-58813000
服务邮箱:service@cnnic.cn
投诉与建议:0668-2555555
Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 ICP核准(ICP备案)粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008