New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
实现 new 操作符 #11
Comments
what is newnew 运算符是一个左值表达式,
我们以 function A(){
this.name = "test"
} 为例, new A() // new MemberExpression Arguments, MemberExpression =>FunctionExpression
new A // new NewExpression, NewExpression =>MemberExpression =>FunctionExpression
new new A // 按上面语法描述,这里是可以这么写的,且不会报语法错误,仅是报了 TypeError
new A.name // TypeError 错误。new NewExpression, NewExpression=> MemberExpression =>MemberExpression . IdentifierName 。 前两种写法均可,在有传参的时候只能使用第一种,同时注意语法解释过程,第四种写法语法解析完变成 new MemberExpression . IdentifierName,即 new "A", 导致异常 我们以无参数调用
先讲下规范,调用函数对象 F 的 [[Construct]] 内部方法时,执行过程如下:
函数对象 A 按上面的规范执行,其执行过程如下:
实现快速实现根据以上描述,我们可以很快的写出如下代码(不考虑异常情况): function _new (F, ...args) {
var obj = Object.create(F.prototype); // 相当于 ({}).__proto__ = F.prototype
var result = F.call(obj, ...args)
return typeof result === "object" ? result : obj
} Type(result) 判断接着我们考虑 ECMAScript 语言类型包括 未定义 (Undefined)、 空值 (Null)、 布尔值(Boolean)、 字符串 (String)、 数值 (Number)、 对象 (Object) 注意1,这里 因此 Type(result) 的实现应该为 (typeof result === 'object' && result !== null ) || typeof result === 'function' 即 function _new (F, ...args) {
var obj = Object.create(F.prototype); // 相当于 ({}).__proto__ = F.prototype
var result = F.call(obj, ...args)
var isESObject = (typeof result === 'object' && result !== null ) || typeof result === 'function'
return isESObject ? result : obj
} 构造函数判断接着考虑异常情况:
var isFunction = typeof constructor === 'function'
if(!isFunction){
throw TypeError(`${constructor} is not a constructor`)
}
我们在外部难以实现 [[Construct]] 构造与否的判断,因此只能根据规律来总结。
var A ={
g:function* (){},
arrow:()=>{},
shorthand(){},
cs:function(){}
}
new A.g // TypeError
new A.arrow // TypeError
new A.shorthand // TypeError
new A.cs // cs {} 对所有方法的 prototype 进行输出,发现 A.g.prototype // Generator {}
A.arrow.prototype // undefined
A.shorthand.prototype // undefined
A.cs.prototype // {constructor: ƒ} 发现构造函数满足该条件 function is_constructor(f){
return !!f && f.hasOwnProperty("prototype") && f.prototype.hasOwnProperty("constructor")
}
new Math.max // TypeError
new String.prototype.indexOf // TypeError 内置函数无 prototype,因此共用上面的判断逻辑即可 值得注意的是还有一个 其不能使用 new 实例化。但是 综合判断如下: function is_constructor(f){
if (f === Symbol) return false;
return !!f && f.hasOwnProperty("prototype") && f.prototype.hasOwnProperty("constructor")
}
// true
is_constructor(function(){});
is_constructor(class A {});
is_constructor(Array);
is_constructor(Function);
is_constructor(new Function);
// false
is_constructor();
is_constructor(undefined);
is_constructor(null);
is_constructor(1);
is_constructor(new Number(1));
is_constructor(Array.prototype);
is_constructor(Function.prototype);
is_constructor(() => {})
is_constructor({method() {}}.method)
is_constructor(Symbol)
is_constructor(Math.max)
is_constructor(String.prototype.indexOf) 但是处理不了手动修改 constructor 值的做法 var a = ()=>{}
a.prototype = {constructor:1}
new a() // TypeError
_new(a) // {} 属性都是可以随意设置的,因此判断属性存在与否是不靠谱的,网上继续搜索, function is_constructor(f) {
// 特殊判断,Symbol 能通过检测
if (f === Symbol) return false;
try {
Reflect.construct(String, [], f);
} catch (e) {
return false;
}
return true;
}
// true
is_constructor(function(){});
is_constructor(class A {});
is_constructor(Array);
is_constructor(Function);
is_constructor(new Function);
// false
is_constructor();
is_constructor(undefined);
is_constructor(null);
is_constructor(1);
is_constructor(new Number(1));
is_constructor(Array.prototype);
is_constructor(Function.prototype);
is_constructor(() => {})
is_constructor({method() {}}.method)
is_constructor(Symbol)
is_constructor(Math.max)
is_constructor(String.prototype.indexOf) 看到这,你一定在想 我们先拿个例子运行下 function A(){
console.log("hh")
}
new A() // 输出hh 返回 A {}
Reflect.construct(A,[]) // 输出hh 返回 A {}
var a = Reflect.construct(String, [], A) // 不输出,a = A {""}
a instanceof A // true
a.toString() // [object String] MDN Reflect.construct
因此,当 f 不是构造函数时,抛出错误;当 f 是构造函数时,也不会执行 f 构造函数导致造成影响 最终实现function _new (F, ...args) {
function is_constructor (f) {
// 特殊判断,Symbol 能通过检测
if (f === Symbol) return false;
try {
Reflect.construct(String, [], f);
} catch (e) {
return false;
}
return true;
}
var isFunction = typeof F === 'function'
if (!isFunction || !is_constructor(F)) {
throw TypeError(`${F.name||F} is not a constructor`)
}
var obj = Object.create(F.prototype); // 相当于 ({}).__proto__ = F.prototype
var result = F.call(obj, ...args)
var isESObject = (typeof result === 'object' && result !== null) || typeof result === 'function'
return isESObject ? result : obj
} 鄙人水平不足,有些知识点可能遗漏或理解错误,欢迎指正~ 拓展阅读 |
测试用例
function A(name){
this.name = name
}
new A("test") // A {name: "test"}
_new (A,"test") // A {name: "test"}
function A(name){
this.name = name
return null
}
new A("test") // {name: "test"}
_new (A,"test") // {name: "test"}
function A(name){
this.name = name
return ()=>{}
}
new A("test") // ()=>{}
_new (A,"test") // ()=>{}
var A = ()=>{}
new A() // Uncaught TypeError: A is not a constructor
_new (A) // Uncaught TypeError: A is not a constructor |
拓展在模拟 new 的基础上,模拟 es6 的 new.target 属性 |
这里模拟的 function Person(name) {
if (_new.target !== undefined) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}
// 预期输出
Person("test") // Uncaught Error: 必须使用 new 命令生成实例
_new(Persion,"test") // Person {name:'test'} 因此我们可以在 function _new (F, ...args) {
function is_constructor (f) {
// 特殊判断,Symbol 能通过检测
if (f === Symbol) return false;
try {
Reflect.construct(String, [], f);
} catch (e) {
return false;
}
return true;
}
var isFunction = typeof F === 'function'
if (!isFunction || !is_constructor(F)) {
throw TypeError(`${F.name||F} is not a constructor`)
}
_new.target = F
var obj = Object.create(F.prototype); // 相当于 ({}).__proto__ = F.prototype
var result = F.call(obj, ...args)
var isESObject = (typeof result === 'object' && result !== null) || typeof result === 'function'
delete _new.target
return isESObject ? result : obj
}
// 测试用例
Person("test") // Uncaught Error: 必须使用 new 命令生成实例
_new(Person,"test") // Person {name: "test"}
Person("test") // Uncaught Error: 必须使用 new 命令生成实例 |
new.target 的模拟实现,发现个问题: 当在构造函数内如果通过 _new 创建了另外的对象,那么这条代码执行之后,就读取不到当前 _new.target 的值了,因为已经被删除了。 function B() {
console.log(_new.target); // B(){}
let b = _new(A);
console.log(_new.target); // undefined
} 需要考虑构造函数中嵌套使用 _new 的场景 |
感谢回复。这个没有考虑到,看来得对 target 做个出栈入栈 |
继续对 模拟 new.target 进行优化。 考虑以下几点:
class Rectangle {
constructor(length, width) {
console.log(new.target === Rectangle);
}
}
class Square extends Rectangle {
constructor(length) {
super(length, width);
}
}
var obj = new Square(3); // 输出 false 对于第 1 点,需要判断当前执行环境,不好处理 2、3 点我们采用 if (!_new.hasOwnProperty('target')) {
// 调用函数栈,假装他是私有属性
_new.__stack = []
Object.defineProperty(_new, 'target', {
// 不可删除,不可修改配置
configurable: false,
enumerable: false,
get: function () {
return _new.__stack[_new.__stack.length - 1]
},
set: function () {
// 修改时会抛出异常
throw ReferenceError("Invalid left-hand side in assignment")
}
})
}
_new.__stack.push(F)
//...
_new.__stack.pop() 第 4 点暂不满足,因为 class 只能通过 new 实例化,我们上文的 完善后的代码如下: function _new (F, ...args) {
function is_constructor (f) {
// 特殊判断,Symbol 能通过检测
if (f === Symbol) return false;
try {
Reflect.construct(String, [], f);
} catch (e) {
return false;
}
return true;
}
var isFunction = typeof F === 'function'
if (!isFunction || !is_constructor(F)) {
throw TypeError(`${F.name || F} is not a constructor`)
}
if (!_new.hasOwnProperty('target')) {
// 调用函数栈,假装他是私有属性
_new.__stack = []
Object.defineProperty(_new, 'target', {
// 不可删除,不可修改配置
configurable: false,
enumerable: false,
get: function () {
return _new.__stack[_new.__stack.length - 1]
},
set: function () {
// 修改时会抛出异常
throw ReferenceError("Invalid left-hand side in assignment")
}
})
}
_new.__stack.push(F)
var obj = Object.create(F.prototype); // 相当于 ({}).__proto__ = F.prototype
var result = F.call(obj, ...args)
var isESObject = (typeof result === 'object' && result !== null) || typeof result === 'function'
_new.__stack.pop()
return isESObject ? result : obj
} 测试用例 function A () {
console.log(_new.target)
}
function B () {
console.log(_new.target); // B(){}
_new(A); // A(){}
A() // B(){} 注意这里, 用 new.target 的时候应该是
console.log(_new.target); // B(){}
try {
_new.target = 1
} catch (error) {
console.log(error) // Uncaught ReferenceError: Invalid left-hand side in assignment
}
console.log(_new.target); // B(){}
}
_new(B) 基本符合要求,但是
以下提供一个新思路 e.stack 大概过程就是,利用 尝试这个例子 function _new () {
try {
throw Error("test")
} catch (e) {
console.log(e.stack)
}
}
function B(){
_new()
}
B() chrome 上输出
ff 上输出
我们可以根据正则获取 由于这些都是 hack 操作,并不能实现 100% 正确,这里也就简单提供个思路,读者可以自行尝试 总的来说,完全模拟
|
_new 没有处理 bound 函数 var a = {
say:function(){}
}
var bound = a.say.bind(a)
new bound // say {}
_new(bound) // TypeError: Object prototype may only be an Object or null: undefined 分析原因是 bound 函数没有 prototype 属性 Object.create(F.prototype); 这段代码执行就报错了 |
该问题无法处理,原因在于 bound 函数的 thisArg 不会被外部改变。 比如 var A = {
name: "test",
cs: function (sex, age) { return {name:this.name,sex,age} }
}
var boundCs = A.cs.bind(A)
new boundCs (1,1) // {name: undefined, sex: 1, age: 1}
boundCs .call({},1,1) // {name: "test", sex: 1, age: 1} boundCs 中的 this 始终和初次 bind 时指定的 this 一样,无法改变 |
实现 _new,达到如下效果
The text was updated successfully, but these errors were encountered: