咨询Safari与Chrome中this.toSource()的等效替代实现方案
toSource()方法以兼容Chrome/Safari等浏览器? 我有一段仅能在Firefox中正常运行的代码,原因是
this.toSource()方法在Safari和Chrome浏览器中无法生效。请问如何编写一个可兼容Safari、Chrome及其他部分浏览器的替代函数?我曾尝试使用this.toString()进行替换,但未能达到预期效果。以下是我的代码:
var elements = []; var rect = function() { this.x = 0; this.y = 0; this.width = 100; this.height = 100; this.background = "black"; this.name = "rect" }; rect.prototype.render = function() { if(this.toSource() != null) { elements[elements.length] = this.toSource(); } // c = this.toString(); // elements[elements.length] = c; var returnThing; for(var i in this) { returnThing += this[i] } returnThing = returnThing.replace(/NaNblackrect3D30/g, ''); returnThing = returnThing.replace(/function()/g, ''); returnThing = returnThing.replace(") {", ''); elements[elements.length] = returnThing; };
这个问题很常见——Firefox的toSource()是个非标准方法,Chrome和Safari根本不支持,而且你用toString()的思路本身就不对(默认toString()只会返回"[object Object]"),手动拼接属性值再靠正则擦屁股的方式也太脆弱了。咱们来写个靠谱的兼容方案:
方案1:自定义一个toSource替代函数
这个函数会模拟Firefox toSource()的行为,返回对象的字面量形式,同时只处理对象自身的属性(不会混入原型链上的方法):
function objectToSource(obj) { // 处理null和基本类型 if (obj === null) return 'null'; if (typeof obj !== 'object') return String(obj); // 处理数组 if (Array.isArray(obj)) { return '[' + obj.map(item => objectToSource(item)).join(', ') + ']'; } // 处理普通对象 const props = []; for (const key in obj) { // 只取对象自己的属性,跳过原型上的(比如你的render方法) if (obj.hasOwnProperty(key)) { // 处理属性名:如果是合法标识符就直接用,否则加引号 const keyStr = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `"${key}"`; const valueStr = objectToSource(obj[key]); props.push(`${keyStr}: ${valueStr}`); } } return `({${props.join(', ')}})`; }
然后修改你的render方法,用这个函数替代原生toSource():
rect.prototype.render = function() { // 用自定义函数生成兼容的对象字面量字符串 const sourceStr = objectToSource(this); elements.push(sourceStr); // 你原来的手动拼接和正则替换逻辑可以完全删掉了,上面的函数已经能生成正确的结果 };
方案2:用JSON.stringify(适合纯数据对象)
如果你的rect对象里没有函数、undefined这类JSON不支持的类型,直接用JSON.stringify会更简单,只要稍微调整格式就能接近toSource()的输出:
rect.prototype.render = function() { // JSON.stringify会自动忽略原型属性,只序列化自身可枚举属性 const jsonStr = JSON.stringify(this); // 给JSON字符串套个括号,模拟toSource()的格式 const sourceStr = `(${jsonStr})`; elements.push(sourceStr); };
为什么你的toString()尝试没用?
Object.prototype.toString()的默认行为是返回对象的类型标识(比如"[object Object]"),只有当你给对象自定义了toString方法时,才会返回你想要的内容。显然你的rect实例没有自定义这个方法,所以完全达不到toSource()的效果。而你手动遍历拼接的方式,本质是把所有属性值拼接成一个长字符串,再靠正则去修正,这种方式完全依赖固定的属性值顺序和内容,一旦属性值变化,正则就失效了,非常不可靠。
内容的提问来源于stack exchange,提问作者Ronan Leonard

