Typescript & Javascript 开发小记 写一个简单的沙箱

深拷贝和浅拷贝是 JS 基础,很多人(包括我)可能理解两者的区别。
还有记录一下近期开发沙盒的过程。

中数组的深拷贝和浅拷贝

{% codeblock lang:js %}
// 现在有个数组 list,它里面有“a、b、c”,3 个变量,如下:
const a = {
name: 'a',
value: 'a'
};

const b = 'str';
let c = 256;

const list = [a, b, c];
const listCopy = list;
const listDeep = _.cloneDeep(list);
const listClone = _.clone(list);
list.push('d');
a.name = 'new a';
listClone.push('e');
c = 480;
listDeep.push('f');
list[0].value = 'new a';
list[1] = 'new str';

console.log('list', list); // ???
console.log('listCopy', listCopy); // ???
console.log('listDeep', listDeep); // ???
console.log('listClone', listClone); // ???
{% endcodeblock %}

重要概念

  • 基本类型:Null、Undefined、Boolean、Number、String、Symbol。
  • 引用类型:Object、Array、Function、Date 等。

基本类型和引用类型的区别:基本类型在内存中是直接存储数据的,引用类型则为一个内存地址,通过这个地址可以得到存储的数据。

结果及注释

{% codeblock lang:js %}
/**

  • 为方便讲解和学习,引入 lodash 的深拷贝和浅拷贝函数
  • 以下代码可在浏览器控制台运行
    */
    const importJS = document.createElement('script');
    importJS.setAttribute("src", 'https://fastly.i5res.com/npm/lodash@4.17.21/lodash.min.js');
    document.getElementsByTagName("head")[0].appendChild(importJS);

// 等待 lodash 加载。要在浏览器使用,有什么好的办法吗?
setTimeout(() => {
if (typeof _ === 'function') {
// 现在有个数组 list,它里面有“a、b、c”,3 个变量,如下:
const a = {
name: 'a',
value: 'a'
};

const b = 'str';
let c = 256;

const list = [a, b, c];
const listCopy = list; // 引用赋值
const listDeep = _.cloneDeep(list); // 深拷贝
const listClone = _.clone(list); // 浅拷贝
list.push('d'); // list、 listCopy(赋值) 受影响
a.name = 'new a'; // list、listClone(浅) 和 listCopy(赋值) 受影响
listClone.push('e'); // listClone(浅)受影响
c = 480; // 都不受影响
listDeep.push('f'); // listDeep(深)受影响
list[0].value = 'new a'; // list、listClone(浅) 和 listCopy(赋值) 受影响
list[1] = 'new str'; // list、listCopy(赋值) 受影响

console.log('list', list); // [{name: "new a", value: "new a"}, "new str", 256, "d"]
console.log('listCopy', listCopy); // [{name: "new a", value: "new a"}, "new str", 256, "d"]
console.log('listDeep', listDeep); // [{name: "a", value: "a"}, "str", 256, "f"]
console.log('listClone', listClone); // [{name: "new a", value: "new a"}, "str", 256, "e"]

}
}, 1000);
{% endcodeblock %}

总结

  1. 浅拷贝得到的数组,其内引用类型的地址为原来的地址,基本类型没有引用地址,等同于深拷贝。即:浅拷贝后数组中的引用类型跟随原数组变化。
  2. 深拷贝得到的数组,引用类型的地址不为原来的地址,即:其每层引用类型的地址都是新的地址,所有数据变化不与原来的关联。
  3. 引用赋值得到的数组,与原来的数组一致,即:list 和 listCopy 指向同一个地址,可以得到原数组的引用地址内的数据变化。

沙盒开发

核心功能

导入模块vm模块

官方文档

主要内容

上下文

这个作为沙盒的运行主体(什么都要往里送)

context

{% codeblock lang:js %}
//把context包装成proxy对象,来捕捉一些操作
let context = new Proxy(context, {
set(o, k, v) {
//...
return Reflect.set(o, k, v)
},
defineProperty: (o, k, d)=>{
if (
//权限判断
)
return Reflect.defineProperty(o, k, d)
else
return false
},
preventExtensions: (o)=>{
return false
},
setPrototypeOf: (o, prototype)=>{
return false
}
});
{% endcodeblock %}

保护类

Proxy
Object
Function
Array
...

{% codeblock lang:js %}
//冻结内置对象(不包括console,globalThis)
const internal_properties = [
'Object', 'Function', 'Array',
'Number', 'parseFloat', 'parseInt',
'Boolean', 'String', 'Symbol',
'Date', 'RegExp', 'eval',
'Error', 'EvalError', 'RangeError',
'ReferenceError', 'SyntaxError', 'TypeError',
'URIError', 'JSON', 'Promise',
'Math', 'Intl',
'ArrayBuffer', 'Uint8Array', 'Int8Array',
'Uint16Array', 'Int16Array', 'Uint32Array',
'Int32Array', 'Float32Array', 'Float64Array',
'Uint8ClampedArray', 'BigUint64Array', 'BigInt64Array',
'DataView', 'Map', 'BigInt',
'Set', 'WeakMap', 'WeakSet',
'Proxy', 'Reflect', 'decodeURI',
'decodeURIComponent', 'encodeURI', 'encodeURIComponent',
'escape', 'unescape',
'isFinite', 'isNaN', 'SharedArrayBuffer',
'Atomics', 'WebAssembly'
];

for (let v of internal_properties) {
vm.runInContext(this.Object.freeze(this.${v}); this.Object.freeze(this.${v}.prototype); const ${v} = this.${v}, context);
}
{% endcodeblock %}

权限类

isMaster()

{% codeblock lang:js %}
// SANDBOX_ROOT该环境变量为永久master
vm.runInContext(Object.defineProperty(this, "root", { configurable: false, enumerable: false, writable: false, value: "xxxxxxxx" // 主人的鉴权方式 }), context);
{% endcodeblock %}

输入

这边是封装了一个exec的函数

{% codeblock lang:js %}
//沙盒执行超时时间
let timeout = 500
module.exports.getTimeout = (t)=>timeout
module.exports.setTimeout = (t)=>timeout=t
{% endcodeblock %}

{% codeblock lang:js %}
module.exports.exec = (code) => {
return vm.runInContext(code, context, {timeout: timeout})
}
{% endcodeblock %}

输出

直接用一个try{}包起来,万事大吉

道高一尺

{% codeblock lang:js %}
//防止沙盒逃逸
Function.prototype.view = Function.prototype.toString
Function.prototype.constructor = new Proxy(Function, {
apply: ()=>{
throw Error("想跟妾身斗,汝还差得远呢。")
},
constructor: ()=>{
throw Error("想跟妾身斗,汝还差得远呢。")
}
})
Object.freeze(Object)
Object.freeze(Object.prototype)
Object.freeze(Function)
Object.freeze(Function.prototype)
{% endcodeblock %}