JS中new操作手写函数的实现是怎样的
Admin 2022-08-13 群英技术资讯 275 次浏览
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进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
在jquery中,可以利用css()方法来控制css,给元素添加“display: none;”样式,进而删除该元素;语法格式为“$("元素").css("display", "none");”。
这篇文章主要给大家介绍了关于JavaScript实现的七种排序算法的相关资料,七种排序算法分别是:冒泡排序、选择排序、插入排序、希尔排序、堆排序、快速排序以及归并排序,需要的朋友可以参考下
这篇文章主要介绍了如何实现无感刷新token,当token过期的时候,刷新token,前端需要做到无感刷新token,即刷token时要做到用户无感知,避免频繁登录。下面我们大家一起进入文章看看实现思路即详细内容,需要的朋友可以参考一下
案例数据使用localStorage持久性存储 全局过滤器实现时间格式化代码部分!DOCTYPE htmlhtml lang=zh head meta charset=UTF-8 meta name=viewport content=width=device-width, initia
这篇文章我们来简单了解JavaScript数据可视化库,对于 JS 开发人员来说,可视化数据的能力与制作交互式网页一样有价值。下文给大家介绍了很多受欢迎或有趣的JavaScript数据可视化库,感兴趣的朋友就继续往下看吧。
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008