Skip to content
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

JavaScript深入之call和apply的模拟实现 #11

Open
mqyqingfeng opened this issue May 2, 2017 · 170 comments
Open

JavaScript深入之call和apply的模拟实现 #11

mqyqingfeng opened this issue May 2, 2017 · 170 comments
Labels

Comments

@mqyqingfeng
Copy link
Owner

@mqyqingfeng mqyqingfeng commented May 2, 2017

call

一句话介绍 call:

call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

举个例子:

var foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}

bar.call(foo); // 1

注意两点:

  1. call 改变了 this 的指向,指向到 foo
  2. bar 函数执行了

模拟实现第一步

那么我们该怎么模拟实现这两个效果呢?

试想当调用 call 的时候,把 foo 对象改造成如下:

var foo = {
    value: 1,
    bar: function() {
        console.log(this.value)
    }
};

foo.bar(); // 1

这个时候 this 就指向了 foo,是不是很简单呢?

但是这样却给 foo 对象本身添加了一个属性,这可不行呐!

不过也不用担心,我们用 delete 再删除它不就好了~

所以我们模拟的步骤可以分为:

  1. 将函数设为对象的属性
  2. 执行该函数
  3. 删除该函数

以上个例子为例,就是:

// 第一步
foo.fn = bar
// 第二步
foo.fn()
// 第三步
delete foo.fn

fn 是对象的属性名,反正最后也要删除它,所以起成什么都无所谓。

根据这个思路,我们可以尝试着去写第一版的 call2 函数:

// 第一版
Function.prototype.call2 = function(context) {
    // 首先要获取调用call的函数,用this可以获取
    context.fn = this;
    context.fn();
    delete context.fn;
}

// 测试一下
var foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}

bar.call2(foo); // 1

正好可以打印 1 哎!是不是很开心!(~ ̄▽ ̄)~

模拟实现第二步

最一开始也讲了,call 函数还能给定参数执行函数。举个例子:

var foo = {
    value: 1
};

function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}

bar.call(foo, 'kevin', 18);
// kevin
// 18
// 1

注意:传入的参数并不确定,这可咋办?

不急,我们可以从 Arguments 对象中取值,取出第二个到最后一个参数,然后放到一个数组里。

比如这样:

// 以上个例子为例,此时的arguments为:
// arguments = {
//      0: foo,
//      1: 'kevin',
//      2: 18,
//      length: 3
// }
// 因为arguments是类数组对象,所以可以用for循环
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
    args.push('arguments[' + i + ']');
}

// 执行后 args为 ["arguments[1]", "arguments[2]", "arguments[3]"]

不定长的参数问题解决了,我们接着要把这个参数数组放到要执行的函数的参数里面去。

// 将数组里的元素作为多个参数放进函数的形参里
context.fn(args.join(','))
// (O_o)??
// 这个方法肯定是不行的啦!!!

也许有人想到用 ES6 的方法,不过 call 是 ES3 的方法,我们为了模拟实现一个 ES3 的方法,要用到ES6的方法,好像……,嗯,也可以啦。但是我们这次用 eval 方法拼成一个函数,类似于这样:

eval('context.fn(' + args +')')

这里 args 会自动调用 Array.toString() 这个方法。

所以我们的第二版克服了两个大问题,代码如下:

// 第二版
Function.prototype.call2 = function(context) {
    context.fn = this;
    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }
    eval('context.fn(' + args +')');
    delete context.fn;
}

// 测试一下
var foo = {
    value: 1
};

function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}

bar.call2(foo, 'kevin', 18); 
// kevin
// 18
// 1

(๑•̀ㅂ•́)و✧

模拟实现第三步

模拟代码已经完成 80%,还有两个小点要注意:

1.this 参数可以传 null,当为 null 的时候,视为指向 window

举个例子:

var value = 1;

function bar() {
    console.log(this.value);
}

bar.call(null); // 1

虽然这个例子本身不使用 call,结果依然一样。

2.函数是可以有返回值的!

举个例子:

var obj = {
    value: 1
}

function bar(name, age) {
    return {
        value: this.value,
        name: name,
        age: age
    }
}

console.log(bar.call(obj, 'kevin', 18));
// Object {
//    value: 1,
//    name: 'kevin',
//    age: 18
// }

不过都很好解决,让我们直接看第三版也就是最后一版的代码:

// 第三版
Function.prototype.call2 = function (context) {
    var context = context || window;
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }

    var result = eval('context.fn(' + args +')');

    delete context.fn
    return result;
}

// 测试一下
var value = 2;

var obj = {
    value: 1
}

function bar(name, age) {
    console.log(this.value);
    return {
        value: this.value,
        name: name,
        age: age
    }
}

bar.call2(null); // 2

console.log(bar.call2(obj, 'kevin', 18));
// 1
// Object {
//    value: 1,
//    name: 'kevin',
//    age: 18
// }

到此,我们完成了 call 的模拟实现,给自己一个赞 b( ̄▽ ̄)d

apply的模拟实现

apply 的实现跟 call 类似,在这里直接给代码,代码来自于知乎 @郑航的实现:

Function.prototype.apply = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
}

下一篇文章

JavaScript深入之bind的模拟实现

重要参考

知乎问题 不能使用call、apply、bind,如何用 js 实现 call 或者 apply 的功能?

深入系列

JavaScript深入系列目录地址:https://github.com/mqyqingfeng/Blog

JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。

@mqyqingfeng mqyqingfeng changed the title avaScript深入之call和apply的模拟实现 JavaScript深入之call和apply的模拟实现 May 2, 2017
@JuniorTour
Copy link

@JuniorTour JuniorTour commented May 4, 2017

受益匪浅!学到了很多,谢谢前辈!
有一个小问题:call2第三版和apply的函数内,是不是不必要 var context =...,直接context=...即可?

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented May 4, 2017

哈哈,确实可以,没有注意到这点,感谢指出,(๑•̀ㅂ•́)و✧

@fantasy123
Copy link

@fantasy123 fantasy123 commented May 17, 2017

arr.push('arguments['+i+']');
请问这里为什么是一个拼接操作呢?

@jawil
Copy link

@jawil jawil commented May 17, 2017

eval函数接收参数是个字符串

定义和用法

eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。

语法:
eval(string)

string必需。要计算的字符串,其中含有要计算的 JavaScript 表达式或要执行的语句。该方法只接受原始字符串作为参数,如果 string 参数不是原始字符串,那么该方法将不作任何改变地返回。因此请不要为 eval() 函数传递 String 对象来作为参数。

简单来说吧,就是用JavaScript的解析引擎来解析这一堆字符串里面的内容,这么说吧,你可以这么理解,你把eval看成是<script>标签。

eval('function Test(a,b,c,d){console.log(a,b,c,d)};Test(1,2,3,4)')
@fantasy123

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented May 17, 2017

@jawil 感谢回答哈~
@fantasy123 最终目的是为了拼出一个参数字符串,我们一步一步看:

var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
}

最终的数组为:

var args = [arguments[1], arguments[2], ...]

然后

 var result = eval('context.fn(' + args +')');

在eval中,args 自动调用 args.toString()方法,eval的效果如 jawil所说,最终的效果相当于:

 var result = context.fn(arguments[1], arguments[2], ...);

这样就做到了把传给call的参数传递给了context.fn函数

@lz-lee
Copy link

@lz-lee lz-lee commented May 29, 2017

apply郑航的实现,循环是不是应该从 i = 1 开始?

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented May 29, 2017

@lz-lee call 的实现中,是通过 arguments 取各个参数,所以从 1 开始,省略掉为 0 的 context,而apply的实现中,arr 直接就表示参数的数组,循环这个参数数组,直接就从 0 开始。

@lz-lee
Copy link

@lz-lee lz-lee commented May 29, 2017

粗心大意了。感谢提醒。@mqyqingfeng

@lynn1824
Copy link

@lynn1824 lynn1824 commented May 31, 2017

分析的很透彻,点个赞!

@qianlongo
Copy link

@qianlongo qianlongo commented May 31, 2017

赞赞赞

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jun 2, 2017

@lynn1824 @qianlongo 感谢夸奖,写的时候我就觉得模拟实现一遍 call 和 apply 最能让大家明白 call 和 apply 的原理 (~ ̄▽ ̄)~

@lzr900515
Copy link

@lzr900515 lzr900515 commented Jun 5, 2017

var context = Object(context) || window; 这里有问题吗?context为null时Object(null)返回空对象,不会被赋值为window

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jun 6, 2017

@lzr900515 没有什么问题哈,非严格模式下,指定为 null 或 undefined 时会自动指向全局对象,郑航写的是严格模式下的,我写的是非严格模式下的,实际上现在的模拟代码有一点没有覆盖,就是当值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。

@hujiulong
Copy link

@hujiulong hujiulong commented Jun 13, 2017

context.fn = this;这里似乎漏掉了一个很关键的问题,如果context本来就有fn这个成员怎么办。这里只能给一个原来不存在的名字

var id = 0;
while ( context[ id ] ) {
    id ++;
}
context[ id ] = this;

不过这个方法似乎有点傻

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jun 13, 2017

@hujiulong 哈哈,有道理哈~ 确实会覆盖之前对象的方法,还好模拟实现 call 和 apply 的目的在于让大家通过模拟实现了解 call 和 apply 的原理,实际开发的时候还是要直接使用 call 和 apply 的~

@libin1991
Copy link

@libin1991 libin1991 commented Jun 26, 2017

实在是佩服!

@jasperchou
Copy link

@jasperchou jasperchou commented Jul 17, 2017

// 以上个例子为例,此时的arguments为:
// arguments = {
//      0: foo,
//      1: 'kevin',
//      2: 18,
//      length: 3
// }
// 因为arguments是类数组对象,所以可以用for循环
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
    args.push('arguments[' + i + ']');
}

// 执行后 args为 [foo, 'kevin', 18]

// 执行后 args为 [foo, 'kevin', 18]

这一句可能造成误导。结果为:["arguments[1]", "arguments[2]"]
虽然后面确实会用eval执行,但是此处还没有。

@xsfxtsxxr
Copy link

@xsfxtsxxr xsfxtsxxr commented Jun 30, 2020

第三版

Function.prototype.call2 = function (context) {
    // 增加一条判断,否则在Node环境下会报错,用原生的call就不会报错,所以这边对windows要判断一下
    var window = window || global; 
    var context = context || window;
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }

    var result = eval('context.fn(' + args +')');

    delete context.fn
    return result;
}
@ZhaoTim
Copy link

@ZhaoTim ZhaoTim commented Jul 1, 2020

@whxcode 对基本类型进行包装可以用Object()

        /**
         * @description: 实现call方法
         * @param : context this要绑定的值
         * @param : args 除第一个参数外的参数集合
         * @return: 函数返回值
         */
        Function.prototype.myCall=function(context,...args) {
            let handler=Symbol();// 生成一个唯一的值,用来作为要绑定对象的属性key,储存当前调用call方法的函数

            // 如果第一个参数为引用类型或者null
            if(typeof context==='object') {
                // 如果为null 则this为window
                context=context||window;
            } else {
                // 如果为undefined 则this绑定为window
                if(typeof context==='undefined') {
                    context=window;
                } else {
                    // 基本类型包装  1 => Number{1}
                    context=Object(context);
                }
            }

            // this 为当前调用call方法的函数。
            context.handler=this;
            // 执行这个函数。这时这个函数内部this绑定为cxt,储存函数执行后的返回值。
            let result=context.handler(...args);
            // 删除对象上的函数
            delete context.handler;
            // 返回结果
            return result;
        }

这一行确定是可以的吗.....

context.handler=this;

是不是应该用context[handler]=this;啊?

@returnMaize
Copy link

@returnMaize returnMaize commented Jul 9, 2020

我参考 MDN 对 call 方法的介绍实现了一个

thisArg
可选的。在 function 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
arg1, arg2, ...
指定的参数列表。

Function.prototype.$call = function (context, ...arg) {
  const contextIsPrimitive = context instanceof Object
  const primitiveClassObj = {
    'boolean': Boolean,
    'string': String,
    'number': Number
  }
  if (!contextIsPrimitive) {
    const contextType = typeof context
    context = ['undefined', 'object'].includes(contextType) ? window : new primitiveClassObj[contextType](context)
  }
  context.fn = this
  const res = context.fn(...arg)
  delete context.fn
  return res
}

但是我发现不管怎么实现,这种实现方法依然会存在一个潜在问题,就是当函数调用 $call 方法时,在函数执行期间,context上始终存在 fn,只有当函数执行完成之后 fn 才会从 context 上删除。这样就会导致我们在函数内部访问 context 时和传进函数时的context 永远不会相等。

@returnMaize
Copy link

@returnMaize returnMaize commented Jul 9, 2020

为此,我想到了一个新的实现方法 ,但存在一些性能上的问题

Function.prototype.$call = function (context, ...arg) {
  const contextIsPrimitive = context instanceof Object
  const primitiveClassObj = {
    'boolean': Boolean,
    'string': String,
    'number': Number
  }
  if (!contextIsPrimitive) {
    const contextType = typeof context
    context = ['undefined', 'object'].includes(contextType) ? window : new primitiveClassObj[contextType](context)
  }
  
  // 所以应该深拷贝 context (但是这里存在一个问题,如果 context 很大,例如window,性能消耗会很大)。这里生拷贝函数我 
  就不写了
    const deepCloneObj = deepClone(context)
    deepCloneObj.fn = this
    return deepCloneObj.fn(...arg)
}
@returnMaize
Copy link

@returnMaize returnMaize commented Jul 9, 2020

这样写好像还存在问题,如果我们在函数内部操作了context(例如修改),不会映射到原context上(深拷贝了context),所以应该再加一层代理,实现两个对象之间的映射(这里可以用Object.definedProperty 或 Proxy)

@zrx1118
Copy link

@zrx1118 zrx1118 commented Aug 1, 2020

我有个疑问,在郑航的apply这个方法实现上,不需要对arr类别做判断么?

Function.prototype.apply2 = function (context, arr) {
var context = Object(context) || window;
context.fn = this;

var result;
if (!arr) {
    result = context.fn();
}
else {
    var args = [];
    for (var i = 0, len = arr.length; i < len; i++) {
        args.push('arr[' + i + ']');
    }
    result = eval('context.fn(' + args + ')')
}

delete context.fn
return result;

}

那么我执行下面的代码:
var obj = {
value: 1
}
function a(name, sex) {
return {
value: this.value,
name: name,
sex: sex
}
}
a.apply2(obj, 'silvia') // {value: 3, name: "s", sex: "i"}
不就不符合预期了么

@Gavinqhk
Copy link

@Gavinqhk Gavinqhk commented Aug 2, 2020

有个小问题想问下apply中可以直接result = context.fn(args);这样吗?

@erdong0604
Copy link

@erdong0604 erdong0604 commented Aug 2, 2020

有个小问题想问下apply中可以直接result = context.fn(args);这样吗?

可以的

@gu12
Copy link

@gu12 gu12 commented Aug 3, 2020

context如果是字符串咋办,应该是context?Object(context) || window

@wweggplant
Copy link

@wweggplant wweggplant commented Aug 6, 2020

Function.prototype.call2 = function () {
    var context = arguments[0] || window
    context.fn = this
    var args = arguments.slice(1)
    var result = eval('context.fn('+args.join(',')+')')
    delete context.fn
    return result
}

一开始想用这样的办法,后来发现arguments要用slice需要用到call 🤣

@Jim-jw
Copy link

@Jim-jw Jim-jw commented Aug 7, 2020

[... arguments].slice(1)

@bigbigDreamer
Copy link

@bigbigDreamer bigbigDreamer commented Oct 14, 2020

image
image

我觉得上面的例子中应该都有我的这一个问题:
call|apply可以用于显示绑定this,但是当显示绑定的时候,动态执行函数,那么理论上,我们内部新建的fn是不应该存留在context中的,但是事实是先执行了函数,此时this中必定会有fn,至于删除,应该是之后发生在内存中的事了。

@anjina
Copy link

@anjina anjina commented Nov 10, 2020

@mqyqingfeng
第三版的call我觉得还存在几个问题没有考虑到:
· MDN里提到若参数值为原始值时this会指向该原始值的自动包装对象。
· 若是对象本身已经存在了要调用的方法,可能会造成冲突覆盖(使用Symbol)。
· 使用扩展运算符,这样代码可以简洁很多。

这是我改写的模拟call实现,有错的地方请大佬指出。

Function.prototype._call = function(...arg) {
  let context = arg[0] || window; 
  if(typeof context !== 'object') {
    context = Object.create(null); 
  }
  let fn = Symbol();
  context[fn] = this;  
  let result = context[fn](...arg.slice(1));
  delete context[fn];
  return result;   
}

let name = "window";
let obj = {
  name: "local"
}    
function show(param) {
  console.log(this.name);
  console.log(param);
}
show._call(obj, "dangosky");

你这里第二点参数值为原始类型值时,会包装成原始类型的对象。比如fn.call(2) == fn.call(new Number(2))
我看你的处理是使用Object.create(null)创建空对象应该有问题

@1415333545
Copy link

@1415333545 1415333545 commented Nov 16, 2020

怎么提问呀

@1415333545
Copy link

@1415333545 1415333545 commented Nov 16, 2020

这样实现怎么样

Function.prototype.myCall = function() {
    const args = []
    for(let i = 0; i< arguments.length; i++) {
        args.push(arguments[i])
    }
    /**
     * 没有传入上下文 或者传入的为 null 就认为上下文是 window
     */
    const context = args.shift() || window
    /**
     * 修改原形了链 不把新增的属性添加到 需要绑定的 上下文环境中 、也避免不小心删除了 绑定上下文中的 方法
     * 即 再 context 中 打印 this 不会出现 _fn 这个函数
     * 但是这个操作比较耗时间
     */
    const prototype = Object.getPrototypeOf(context)
    prototype._fn = this
    Object.setPrototypeOf(context, prototype)
    const result = eval('context._fn('+ args +')')
    delete prototype._fn
    return result
}
@anjina
Copy link

@anjina anjina commented Nov 16, 2020

这样实现怎么样

Function.prototype.myCall = function() {
    const args = []
    for(let i = 0; i< arguments.length; i++) {
        args.push(arguments[i])
    }
    /**
     * 没有传入上下文 或者传入的为 null 就认为上下文是 window
     */
    const context = args.shift() || window
    /**
     * 修改原形了链 不把新增的属性添加到 需要绑定的 上下文环境中 、也避免不小心删除了 绑定上下文中的 方法
     * 即 再 context 中 打印 this 不会出现 _fn 这个函数
     * 但是这个操作比较耗时间
     */
    const prototype = Object.getPrototypeOf(context)
    prototype._fn = this
    Object.setPrototypeOf(context, prototype)
    const result = eval('context._fn('+ args +')')
    delete prototype._fn
    return result
}

@1415333545 试了下你的写法,发现两个问题

  1. context的判断中没有兼容到 0 或者 空字符串的情况,这两种情况下你的this会指向window,而实际的call会指向包装后的对象 Number(0) 或者String('')
  2. args数组不能保存真实值,而应该保存['arguments[0]', 'argument[1]']这种字符串,在eval 的时候 就相当于执行 context._fn(arguments[0], arguments[1]) 你那种保存方式 在 参数为引用类型时会有问题,比如[].toString() === ''
@whxcode
Copy link

@whxcode whxcode commented Nov 17, 2020

谢谢

This was referenced Nov 17, 2020
@wenwen1995
Copy link

@wenwen1995 wenwen1995 commented Dec 4, 2020

var foo = {
    value: 1
};

function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
    console.log(arguments);  //新加的打印arguments的log 语句
}

bar.call(foo, 'kevin', 18);

图为证:
图片

上述代码中,使用原本的call方法,打印arguments时,得到的长度是2而不是作者大大说的3,这是为啥呢?如果是这样的话,为啥之后重写这个call函数中 arguments中得到的结果又是正确的呢?

@louiebb
Copy link

@louiebb louiebb commented Dec 4, 2020

hello @wenwen1995 很高兴回答你的问题,这是作用域不一样,作者所描述的作用域是执行时的作用域,而你打印的是bar的作用域

@wenwen1995
Copy link

@wenwen1995 wenwen1995 commented Dec 4, 2020

hello @wenwen1995 很高兴回答你的问题,这是作用域不一样,作者所描述的作用域是执行时的作用域,而你打印的是bar的作用域

嗷嗷。明白啦~谢谢你

@anjina
Copy link

@anjina anjina commented Dec 4, 2020

var foo = {
    value: 1
};

function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
    console.log(arguments);  //新加的打印arguments的log 语句
}

bar.call(foo, 'kevin', 18);

图为证:
图片

上述代码中,使用原本的call方法,打印arguments时,得到的长度是2而不是作者大大说的3,这是为啥呢?如果是这样的话,为啥之后重写这个call函数中 arguments中得到的结果又是正确的呢?

call 函数里面执行时还多了一个 绑定的对象 (this) 参数

@MrGuojunwei
Copy link

@MrGuojunwei MrGuojunwei commented Dec 15, 2020

针对node中不存在window全局对象、context为null或undefined时context指向全局对象、context为基本类型值时context为基本值的对象类型等情况,可以添加一个对context的判断逻辑。具体如下

function call(context) { 
   const global = window || global;
   if (context == undefined) { 
        context = global;
     }else if (typeof context !== 'object' && typeof context !== 'function') { 
         context = Object(context);
     }
      context.fn = this; 
      var args = []; 
      for (var i = 1; i < arguments.length; i++) { 
          args.push('arguments[' + i + ']');
       } 
       var result = eval('context.fn(' + args + ')');
       delete context.fn; 
       return result;
    }
@888hql888
Copy link

@888hql888 888hql888 commented Jan 15, 2021

我丢 真有意思

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.