整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          什么是一篇高質量的臨床研究論文?(4)寫論文

          什么是一篇高質量的臨床研究論文?(4)寫論文

          站上不乏關于如何寫各種學術醫學文章的一般性建議。最具影響力的資源是提交給生物醫學期刊的稿件的統一要求(ICMJE http://www.icmje.org/urm_main.html),這是坐下來寫論文的最佳開始。這些指導方針是在30年前提出的,目的是為了避免作者在按順序提交到幾家普通醫學期刊時,完全重新格式化手稿的麻煩。但現在這些統一的要求已經被800多種期刊所采用,包括口腔疾病(http://www。wiley.com/bw/submit.asp?ref=1354-523X&site=1),和他們包括了廣泛的關于出版實踐和倫理的編輯政策和建議。來自WAME(世界醫學編輯協會)、CSE(科學編輯理事會)和COPE的指導意見呼應和補充了統一的要求。

          利益沖突

          國際醫學期刊編輯委員會的最新貢獻是相互競爭利益的統一聲明(http://www.icmje.org/coi_disclosure.pdf),委員會將試點到2010年4月,然后根據任何重要的批評進行修改(Drazen et al,2009)。這要求作者披露四種類型的信息首先,他們與商業實體對提交的手稿中報告的工作提供支持(在表格的這一節中披露的時間框架是被報告的工作的壽命)。其次,他們與商業實體的聯系可以被視為對提交的手稿的一般領域有興趣(在本節中披露的時間框架是提交手稿前的36個月)。第三,涉及其配偶或其18歲以下子女的任何類似的財務協會。第四,可能與提交的手稿相關的非財務協會。

          如果研究人員遵循到目前為止這里強調的所有建議,他們應該有一篇好的論文。最后兩份工作是找到合適的期刊,理想情況下,選擇主要基于合適的讀者,而不僅僅是期刊的聲望或影響因素,然后非常仔細地按照期刊對作者的指示行事。如果你的論文不符合期刊的格式,你就有可能讓忙碌的編輯和審稿人難以閱讀,而且你可能會在最后一刻錯失機會。

          章來自公眾號@讓前端飛,https://mp.weixin.qq.com/s/H0aXAWBThS8s1ynOwPiSkw

          篇幅略長,歡迎關注/收藏

          什么是“沙箱”

          沙箱(Sandbox)[1]

          也稱作:“沙箱/沙盒/沙盤”。沙箱是一種安全機制,為運行中的程序提供隔離環境。通常是作為一些來源不可信、具破壞力或無法判定程序意圖的程序提供實驗之用。沙箱能夠安全的執行不受信任的代碼,且不影響外部實際代碼影響的獨立環境。

          有哪些動態執行腳本的場景?

          在一些應用中,我們希望給用戶提供插入自定義邏輯的能力,比如 Microsoft 的 Office 中的 VBA,比如一些游戲中的 lua 腳本,FireFox 的「油猴腳本」,能夠讓用戶發在可控的范圍和權限內發揮想象做一些好玩、有用的事情,擴展了能力,滿足用戶的個性化需求。

          大多數都是一些客戶端程序,在一些在線的系統和產品中也常常也有類似的需求,事實上,在線的應用中也有不少提供了自定義腳本的能力,比如 Google Docs 中的 Apps Script,它可以讓你使用 JavaScript 做一些非常有用的事情,比如運行代碼來響應文檔打開事件或單元格更改事件,為公式制作自定義電子表格函數等等。

          與運行在「用戶電腦中」的客戶端應用不同,用戶的自定義腳本通常只能影響用戶自已,而對于在線的應用或服務來講,有一些情況就變得更為重要,比如「安全」,用戶的「自定義腳本」必須嚴格受到限制和隔離,即不能影響到宿主程序,也不能影響到其它用戶。

          另外,有一些牽扯「模板化」的前端框架,如Vue.js、Venom.js等都會用到動態代碼執行。

          JavaScript中的沙箱實現

          零、幾個基礎知識

          什么是constructor

          ?JavaScript中constructor屬性指向創建當前對象的構造函數,該屬性是存在原型里的,且是不可靠的 JavaScript中constructor屬性[2]

          function test() {}
          const obj=new test();
          console.log(obj.hasOwnProperty('constructor')); // false
          console.log(obj.__proto__.hasOwnProperty('constructor')); // true
          console.log(obj.__proto__===test.prototype); // true
          console.log(test.prototype.hasOwnProperty('constructor')); // true
          
          /** constructor是不可靠的 */
          function Foo() {}
          Foo.prototype={};
          const foo=new Foo();
          console.log(foo.constructor===Object);  // true,可以看出不是Foo了

          ?constructor也是一種用于創建和初始化class[3]創建的對象的特殊方法 Class構造方法[4]


          幾個典型的constructor:

          (async function(){})().constructor===Promise
          
          // 瀏覽器環境下
          this.constructor.constructor===Function
          window.constructor.constructor===Function
          
          // node環境下
          this.constructor.constructor===Function
          global.constructor.constructor===Function

          JS Proxy getPrototypeOf()

          handler.getPrototypeOf()是一個代理方法,當讀取代理對象的原型時,該方法就會被調用。語法:

          const p=new Proxy(obj, {
            getPrototypeOf(target) { // target 被代理的目標對象。
            ...
            }
          });

          當 getPrototypeOf 方法被調用時,this 指向的是它所屬的處理器對象,getPrototypeOf 方法的返回值必須是一個對象或者 null。

          在 JavaScript 中,有下面這五種操作(方法/屬性/運算符)可以觸發 JS 引擎讀取一個對象的原型,也就是可以觸發 getPrototypeOf() 代理方法的運行:

          ?Object.getPrototypeOf()[5]?Reflect.getPrototypeOf()[6]?proto[7]?Object.prototype.isPrototypeOf()[8]?instanceof[9]


          如果遇到了下面兩種情況,JS 引擎會拋出 TypeError[10] 異常:

          ?getPrototypeOf() 方法返回的不是對象也不是 null。?目標對象是不可擴展的,且 getPrototypeOf() 方法返回的原型不是目標對象本身的原型


          基本用法:

          const obj={};
          const proto={};
          const handler={
              getPrototypeOf(target) {
                  console.log(target===obj);   // true
                  console.log(this===handler); // true
                  return proto;
              }
          };
          
          var p=new Proxy(obj, handler); // obj是被代理的對象,也就是handler.getPrototypeOf的target參數
          console.log(Object.getPrototypeOf(p)===proto);    // true


          5 種觸發 getPrototypeOf 代理方法的方式:

          const obj={};
          const p=new Proxy(obj, {
              getPrototypeOf(target) {
                  return Array.prototype;
              }
          });
          
          console.log(
              Object.getPrototypeOf(p)===Array.prototype,  // true
              Reflect.getPrototypeOf(p)===Array.prototype, // true
              p.__proto__===Array.prototype,               // true
              Array.prototype.isPrototypeOf(p),              // true
              p instanceof Array                             // true
          );


          兩種異常的情況:

          // getPrototypeOf() 方法返回的不是對象也不是 null
          const obj={};
          const p=new Proxy(obj, {
              getPrototypeOf(target) {
                  return "foo";
              }
          });
          Object.getPrototypeOf(p); // TypeError: "foo" is not an object or null
          
          // 目標對象是不可擴展的,且 getPrototypeOf() 方法返回的原型不是目標對象本身的原型
          const obj=Object.preventExtensions({}); // obj不可擴展
          const p=new Proxy(obj, {
              getPrototypeOf(target) {
                  return {};
              }
          });
          Object.getPrototypeOf(p); // TypeError: expected same prototype value
          
          // 如果對上面的代碼做如下的改造就沒問題
          const obj=Object.preventExtensions({}); // obj不可擴展
          const p=new Proxy(obj, {
              getPrototypeOf(target) { // target就是上面的obj
                  return obj.__proto__; // 返回的是目標對象本身的原型
              }
          });
          Object.getPrototypeOf(p); // 不報錯

          一、跟瀏覽器宿主環境一致的沙箱實現

          構建閉包環境

          我們知道在 JavaScript 中的作用域(scope)只有全局作用域(global scope)、函數作用域(function scope)以及從 ES6 開始才有的塊級作用域(block scope)。如果要將一段代碼中的變量、函數等的定義隔離出來,受限于 JavaScript 對作用域的控制,只能將這段代碼封裝到一個 Function 中,通過使用 function scope 來達到作用域隔離的目的。也因為需要這種使用函數來達到作用域隔離的目的方式,于是就有 IIFE(立即調用函數表達式),這是一個被稱為“自執行匿名函數”的設計模式。

          (function foo(){
              const a=1;
              console.log(a);
           })();// 無法從外部訪問變量 
           
           console.log(a) // 拋出錯誤:"Uncaught ReferenceError: a is not defined"


          當函數變成立即執行的函數表達式時,表達式中的變量不能從外部訪問,它擁有獨立的詞法作用域。不僅避免了外界訪問 IIFE 中的變量,而且又不會污染全局作用域,彌補了 JavaScript 在 scope 方面的缺陷。一般常見于寫插件和類庫時,如 JQuery 當中的沙箱模式

          (function (window) {
              var jQuery=function (selector, context) {
                  return new jQuery.fn.init(selector, context);
              }
              jQuery.fn=jQuery.prototype=function () {
                  //原型上的方法,即所有jQuery對象都可以共享的方法和屬性
              }
              jQuery.fn.init.prototype=jQuery.fn;
              window.jQeury=window.$=jQuery; //如果需要在外界暴露一些屬性或者方法,可以將這些屬性和方法加到window全局對象上去
          })(window);

          當將 IIFE 分配給一個變量,不是存儲 IIFE 本身,而是存儲 IIFE 執行后返回的結果。

          const result=(function () {
              const name="張三";
              return name;
          })();
          
          console.log(result); // "張三"

          原生瀏覽器對象的模擬

          模擬原生瀏覽器對象的目的是為了防止閉包環境,操作原生對象,篡改污染原生環境,完成模擬瀏覽器對象之前我們需要先關注幾個不常用的 API。

          eval

          eval 函數可將字符串轉換為代碼執行,并返回一個或多個值:

          const b=eval("({name:'張三'})");
          console.log(b.name);

          由于 eval 執行的代碼可以訪問閉包和全局范圍,因此就導致了代碼注入的安全問題,因為代碼內部可以沿著作用域鏈往上找,篡改全局變量,這是我們不希望的。

          console.log(eval( this.window===window )); // true 

          補充幾個點:

          ?性能&安全問題,一般不建議在實際業務代碼中引入eval?輔助異步編程框架的windjs大量采用eval的寫法來輔助編程,引發爭議 專訪 Wind.js 作者老趙(上):緣由、思路及發展[11]?瀏覽器環境下,(0, eval)()比eval()的性能要好「目前已經不是了」(0, eval)(‘this’)[12]

          const times=1000;
          const time1='直接引用';
          const time2='間接引用';
          
          let times1=times;
          console.time(time1);
          while(times1--) {
              eval(`199 + 200`);
          }
          console.timeEnd(time1);
          
          let times2=times;
          console.time(time2);
          while(times2--) {
              (0, eval)(`199 + 200`);
          }
          console.timeEnd(time2);

          new Function

          Function構造函數創建一個新的 Function 對象。直接調用這個構造函數可用于動態創建函數。

          new Function ([arg1[, arg2[, ...argN]],] functionBody) 

          arg1, arg2, ... argN 被函數使用的參數的名稱必須是合法命名的。參數名稱是一個有效的 JavaScript 標識符的字符串,或者一個用逗號分隔的有效字符串的列表,例如“×”,“theValue”,或“a,b”。

          補充幾個點:

          ?new Function()性能一般比eval要好,很多用到這塊的前端框架都是用new Function()實現的,比如:Vue.js?打開瀏覽器控制臺后,new Function()的性能要慢一倍以上

          functionBody
          一個含有包括函數定義的 JavaScript 語句的字符串。

          const sum=new Function('a', 'b', 'return a + b'); 
          console.log(sum(1, 2));//3 

          同樣也會遇到和 eval 類似的的安全問題和相對較小的性能問題。

          let a=1;
          
          function sandbox() {
              let a=2;
              return new Function('return a;'); // 這里的 a 指向最上面全局作用域內的 1
          }
          
          const f=sandbox();
          console.log(f());

          與 eval 不同的是 Function 創建的函數只能在全局作用域中運行,它無法訪問局部閉包變量,它們總是被創建于全局環境,因此在運行時它們只能訪問全局變量和自己的局部變量,不能訪問它們被 Function 構造器創建時所在的作用域的變量。new Function()是 eval()更好替代方案。它具有卓越的性能和安全性,但仍沒有解決訪問全局的問題。

          with

          with 是 JavaScript 中一個關鍵字,擴展一個語句的作用域鏈。它允許半沙盒執行。那什么叫半沙盒?語句將某個對象添加到作用域鏈的頂部,如果在沙盒中有某個未使用命名空間的變量,跟作用域鏈中的某個屬性同名,則這個變量將指向這個屬性值。如果沒有同名的屬性,則將拋出 ReferenceError。

          // 嚴格模式下以下代碼運行會有問題
          
          function sandbox(o) {
              with (o){
                  //a=5; 
                  c=2;
                  d=3;
                  console.log(a,b,c,d); // 0,1,2,3 //每個變量首先被認為是一個局部變量,如果局部變量與 obj 對象的某個屬性同名,則這個局部變量會指向 obj 對象屬性。
              }
          }
          
          const f={
              a:0,
              b:1
          }
          sandbox(f); 
                
          console.log(f);
          console.log(c,d); // 2,3 c、d被泄露到window對象上

          究其原理,with在內部使用in運算符。對于塊內的每個變量訪問,它都在沙盒條件下計算變量。如果條件是 true,它將從沙盒中檢索變量。否則,就在全局范圍內查找變量。但是 with 語句使程序在查找變量值時,都是先在指定的對象中查找。所以對于那些本來不是這個對象的屬性的變量,查找起來會很慢,對于有性能要求的程序不適合(JavaScript 引擎會在編譯階段進行數項的性能優化。其中有些優化依賴于能夠根據代碼的詞法進行靜態分析,并預先確定所有變量和函數的定義位置,才能在執行過程中快速找到標識符)。with 也會導致數據泄漏(在非嚴格模式下,會自動在全局作用域創建一個全局變量)

          in 運算符

          in 運算符能夠檢測左側操作數是否為右側操作數的成員。其中,左側操作數是一個字符串,或者可以轉換為字符串的表達式,右側操作數是一個對象或數組。

          const o={  
              a : 1,  
              b : function() {}
          };
          console.log("a" in o);  //true
          console.log("b" in o);  //true
          console.log("c" in o);  //false
          console.log("valueOf" in o);  //返回true,繼承Object的原型方法
          console.log("constructor" in o);  //返回true,繼承Object的原型屬性

          with + new Function

          配合 with 用法可以稍微限制沙盒作用域,先從當前的 with 提供對象查找,但是如果查找不到依然還能從更上面的作用域獲取,污染或篡改全局環境。

          function sandbox (src) {
              src='with (sandbox) {' + src + '}';
              return new Function('sandbox', src);
          }
          
          const str=`
              let a=1; 
              window.name="張三"; 
              console.log(a); // 打?。?
          `;
          
          sandbox(str)({});
          
          console.log(window.name);//'張三'

          可以看到,基于上面的方案都多多少少存在一些安全問題:

          ?eval 是全局對象的一個函數屬性,執行的代碼擁有著和應用中其它正常代碼一樣的的權限,它能訪問「執行上下文」中的局部變量,也能訪問所有「全局變量」,在這個場景下,它是一個非常危險的函數?使用 Function 構造器生成的函數,并不會在創建它的上下文中創建閉包,一般在全局作用域中被創建。當運行函數的時候,只能訪問自己的本地變量和全局變量,不能訪問 Function 構造器被調用生成的上下文的作用域?with 一樣的問題,它首先會在傳入的對象中查找對應的變量,如果找不到就會往更上層的全局作用域去查找,所以也避免不了污染或篡改全局環境

          那有沒有更安全一些的沙箱環境實現呢?

          基于 Proxy 實現的沙箱(ProxySandbox)

          ES6 Proxy 用于修改某些操作的默認行為,等同于在語言層面做出修改,屬于一種“元編程”(meta programming)

          function evalute(code,sandbox) {
            sandbox=sandbox || Object.create(null);
            const fn=new Function('sandbox', `with(sandbox){return (${code})}`);
            const proxy=new Proxy(sandbox, {
              has(target, key) {
                // 讓動態執行的代碼認為屬性已存在
                return true; 
              }
            });
            return fn(proxy);
          }
          evalute('1+2') // 3
          evalute('console.log(1)') // Cannot read property 'log' of undefined

          我們知道無論 eval 還是 function,執行時都會把作用域一層一層向上查找,如果找不到會一直到 global,那么利用 Proxy 的原理就是,讓執行了代碼在 sandobx 中找的到,以達到「防逃逸」的目的。

          我們前面提到with在內部使用in運算符來計算變量,如果條件是 true,它將從沙盒中檢索變量。理想狀態下沒有問題,但也總有些特例獨行的存在,比如 Symbol.unscopables。

          Symbol 對象的 Symbol.unscopables 屬性,指向一個對象。該對象指定了使用 with 關鍵字時,哪些屬性會被 with 環境排除。

          Array.prototype[Symbol.unscopables]
          // {//   copyWithin: true,//   entries: true,//   fill: true,//   find: true,//   findIndex: true,//   keys: true// }Object.keys(Array.prototype[Symbol.unscopables])
          // ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'keys']

          上面代碼說明,數組有 6 個屬性,會被 with 命令排除。


          由此我們的代碼還需要修改如下:

          function sandbox(code) {
              code='with (sandbox) {' + code + '}'
              const fn=new Function('sandbox', code)
          
              return function (sandbox) {
                  const sandboxProxy=new Proxy(sandbox, {
                      has(target, key) {
                          return true
                      },
                      get(target, key) {
                          if (key===Symbol.unscopables) return undefined
                          return target[key]
                      }
                  })
                  return fn(sandboxProxy)
              }
          }
          const test={
              a: 1,
              log(){
                  console.log('11111')
              }
          }
          const code='log(); console.log(a)' // 1111,TypeError: Cannot read property 'log' of undefinedsandbox(code)(test)

          Symbol.unscopables 定義對象的不可作用屬性。Unscopeable 屬性永遠不會從 with 語句中的沙箱對象中檢索,而是直接從閉包或全局范圍中檢索。

          快照沙箱(SnapshotSandbox)

          快照沙箱實現來說比較簡單,主要用于不支持 Proxy 的低版本瀏覽器,原理是基于diff來實現的,在子應用激活或者卸載時分別去通過快照的形式記錄或還原狀態來實現沙箱,snapshotSandbox 會污染全局 window。
          我們看下
          qiankun[13] 的 snapshotSandbox 的源碼,這里為了幫助理解做部分精簡及注釋。

          function iter(obj, callbackFn) {
              for (const prop in obj) {
                  if (obj.hasOwnProperty(prop)) {
                      callbackFn(prop);
                  }
              }
          }
          
          /**
           * 基于 diff 方式實現的沙箱,用于不支持 Proxy 的低版本瀏覽器
           */
          class SnapshotSandbox {
              constructor(name) {
                  this.name=name;
                  this.proxy=window;
                  this.type='Snapshot';
                  this.sandboxRunning=true;
                  this.windowSnapshot={};
                  this.modifyPropsMap={};
                  this.active();
              }
              //激活
              active() {
                  // 記錄當前快照
                  this.windowSnapshot={};
                  iter(window, (prop)=> {
                      this.windowSnapshot[prop]=window[prop];
                  });
          
                  // 恢復之前的變更
                  Object.keys(this.modifyPropsMap).forEach((p)=> {
                      window[p]=this.modifyPropsMap[p];
                  });
          
                  this.sandboxRunning=true;
              }
              //還原
              inactive() {
                  this.modifyPropsMap={};
          
                  iter(window, (prop)=> {
                      if (window[prop] !==this.windowSnapshot[prop]) {
                          // 記錄變更,恢復環境
                          this.modifyPropsMap[prop]=window[prop];
                        
                          window[prop]=this.windowSnapshot[prop];
                      }
                  });
                  this.sandboxRunning=false;
              }
          }
          let sandbox=new SnapshotSandbox();
          //test
          ((window)=> {
              window.name='張三'
              window.age=18
              console.log(window.name, window.age) //    張三,18
              sandbox.inactive() //    還原
              console.log(window.name, window.age) //    undefined,undefined
              sandbox.active() //    激活
              console.log(window.name, window.age) //    張三,18
          })(sandbox.proxy);

          legacySandBox

          qiankun 框架 singular 模式下的 proxy 沙箱實現,為了便于理解,這里做了部分代碼的精簡和注釋。

          //legacySandBox
          const callableFnCacheMap=new WeakMap();
          
          function isCallable(fn) {
            if (callableFnCacheMap.has(fn)) {
              return true;
            }
            const naughtySafari=typeof document.all==='function' && typeof document.all==='undefined';
            const callable=naughtySafari ? typeof fn==='function' && typeof fn !=='undefined' : typeof fn==='function';
            if (callable) {
              callableFnCacheMap.set(fn, callable);
            }
            return callable;
          };
          
          function isPropConfigurable(target, prop) {
            const descriptor=Object.getOwnPropertyDescriptor(target, prop);
            return descriptor ? descriptor.configurable : true;
          }
          
          function setWindowProp(prop, value, toDelete) {
            if (value===undefined && toDelete) {
              delete window[prop];
            } else if (isPropConfigurable(window, prop) && typeof prop !=='symbol') {
              Object.defineProperty(window, prop, {
                writable: true,
                configurable: true
              });
              window[prop]=value;
            }
          }
          
          
          function getTargetValue(target, value) {
            /*
              僅綁定 isCallable && !isBoundedFunction && !isConstructable 的函數對象,如 window.console、window.atob 這類。目前沒有完美的檢測方式,這里通過 prototype 中是否還有可枚舉的拓展方法的方式來判斷
              @warning 這里不要隨意替換成別的判斷方式,因為可能觸發一些 edge case(比如在 lodash.isFunction 在 iframe 上下文中可能由于調用了 top window 對象觸發的安全異常)
             */
            if (isCallable(value) && !isBoundedFunction(value) && !isConstructable(value)) {
              const boundValue=Function.prototype.bind.call(value, target);
              for (const key in value) {
                boundValue[key]=value[key];
              }
              if (value.hasOwnProperty('prototype') && !boundValue.hasOwnProperty('prototype')) {
                Object.defineProperty(boundValue, 'prototype', {
                  value: value.prototype,
                  enumerable: false,
                  writable: true
                });
              }
          
              return boundValue;
            }
          
            return value;
          }
          
          /**
           * 基于 Proxy 實現的沙箱
           */
          class SingularProxySandbox {
            /** 沙箱期間新增的全局變量 */
            addedPropsMapInSandbox=new Map();
          
            /** 沙箱期間更新的全局變量 */
            modifiedPropsOriginalValueMapInSandbox=new Map();
          
            /** 持續記錄更新的(新增和修改的)全局變量的 map,用于在任意時刻做 snapshot */
            currentUpdatedPropsValueMap=new Map();
          
            name;
          
            proxy;
          
            type='LegacyProxy';
          
            sandboxRunning=true;
          
            latestSetProp=null;
          
            active() {
              if (!this.sandboxRunning) {
                this.currentUpdatedPropsValueMap.forEach((v, p)=> setWindowProp(p, v));
              }
          
              this.sandboxRunning=true;
            }
          
            inactive() {
              // console.log(' this.modifiedPropsOriginalValueMapInSandbox', this.modifiedPropsOriginalValueMapInSandbox)
              // console.log(' this.addedPropsMapInSandbox', this.addedPropsMapInSandbox)
              //刪除添加的屬性,修改已有的屬性
              this.modifiedPropsOriginalValueMapInSandbox.forEach((v, p)=> setWindowProp(p, v));
              this.addedPropsMapInSandbox.forEach((_, p)=> setWindowProp(p, undefined, true));
          
              this.sandboxRunning=false;
            }
          
            constructor(name) {
              this.name=name;
              const {
                addedPropsMapInSandbox,
                modifiedPropsOriginalValueMapInSandbox,
                currentUpdatedPropsValueMap
              }=this;
          
              const rawWindow=window;
              //Object.create(null)的方式,傳入一個不含有原型鏈的對象
              const fakeWindow=Object.create(null); 
          
              const proxy=new Proxy(fakeWindow, {
                set: (_, p, value)=> {
                  if (this.sandboxRunning) {
                    if (!rawWindow.hasOwnProperty(p)) {
                      addedPropsMapInSandbox.set(p, value);
                    } else if (!modifiedPropsOriginalValueMapInSandbox.has(p)) {
                      // 如果當前 window 對象存在該屬性,且 record map 中未記錄過,則記錄該屬性初始值
                      const originalValue=rawWindow[p];
                      modifiedPropsOriginalValueMapInSandbox.set(p, originalValue);
                    }
          
                    currentUpdatedPropsValueMap.set(p, value);
                    // 必須重新設置 window 對象保證下次 get 時能拿到已更新的數據
                    rawWindow[p]=value;
          
                    this.latestSetProp=p;
          
                    return true;
                  }
          
                  // 在 strict-mode 下,Proxy 的 handler.set 返回 false 會拋出 TypeError,在沙箱卸載的情況下應該忽略錯誤
                  return true;
                },
          
                get(_, p) {
                  //避免使用 window.window 或者 window.self 逃離沙箱環境,觸發到真實環境
                  if (p==='top' || p==='parent' || p==='window' || p==='self') {
                    return proxy;
                  }
                  const value=rawWindow[p];
                  return getTargetValue(rawWindow, value);
                },
          
                has(_, p) { //返回boolean
                  return p in rawWindow;
                },
          
                getOwnPropertyDescriptor(_, p) {
                  const descriptor=Object.getOwnPropertyDescriptor(rawWindow, p);
                  // 如果屬性不作為目標對象的自身屬性存在,則不能將其設置為不可配置
                  if (descriptor && !descriptor.configurable) {
                    descriptor.configurable=true;
                  }
                  return descriptor;
                },
              });
          
              this.proxy=proxy;
            }
          }
          
          let sandbox=new SingularProxySandbox();
          
          ((window)=> {
            window.name='張三';
            window.age=18;
            window.sex='男';
            console.log(window.name, window.age,window.sex) //    張三,18,男
            sandbox.inactive() //    還原
            console.log(window.name, window.age,window.sex) //    張三,undefined,undefined
            sandbox.active() //    激活
            console.log(window.name, window.age,window.sex) //    張三,18,男
          })(sandbox.proxy); //test

          legacySandBox 還是會操作 window 對象,但是他通過激活沙箱時還原子應用的狀態,卸載時還原主應用的狀態來實現沙箱隔離,同樣會對 window 造成污染,但是性能比快照沙箱好,不用遍歷 window 對象。

          proxySandbox(多例沙箱)

          在 qiankun 的沙箱 proxySandbox 源碼里面是對 fakeWindow 這個對象進行了代理,而這個對象是通過 createFakeWindow 方法得到的,這個方法是將 window 的 document、location、top、window 等等屬性拷貝一份,給到 fakeWindow。

          源碼展示:

          function createFakeWindow(global: Window) {
            // map always has the fastest performance in has check scenario
            // see https://jsperf.com/array-indexof-vs-set-has/23
            const propertiesWithGetter=new Map<PropertyKey, boolean>();
            const fakeWindow={} as FakeWindow;
          
            /*
             copy the non-configurable property of global to fakeWindow
             see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getOwnPropertyDescriptor
             > A property cannot be reported as non-configurable, if it does not exists as an own property of the target object or if it exists as a configurable own property of the target object.
             */
            Object.getOwnPropertyNames(global)
              .filter((p)=> {
                const descriptor=Object.getOwnPropertyDescriptor(global, p);
                return !descriptor?.configurable;
              })
              .forEach((p)=> {
                const descriptor=Object.getOwnPropertyDescriptor(global, p);
                if (descriptor) {
                  const hasGetter=Object.prototype.hasOwnProperty.call(descriptor, 'get');
          
                  /*
                   make top/self/window property configurable and writable, otherwise it will cause TypeError while get trap return.
                   see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/get
                   > The value reported for a property must be the same as the value of the corresponding target object property if the target object property is a non-writable, non-configurable data property.
                   */
                  if (
                    p==='top' ||
                    p==='parent' ||
                    p==='self' ||
                    p==='window' ||
                    (process.env.NODE_ENV==='test' && (p==='mockTop' || p==='mockSafariTop'))
                  ) {
                    descriptor.configurable=true;
                    /*
                     The descriptor of window.window/window.top/window.self in Safari/FF are accessor descriptors, we need to avoid adding a data descriptor while it was
                     Example:
                      Safari/FF: Object.getOwnPropertyDescriptor(window, 'top') -> {get: function, set: undefined, enumerable: true, configurable: false}
                      Chrome: Object.getOwnPropertyDescriptor(window, 'top') -> {value: Window, writable: false, enumerable: true, configurable: false}
                     */
                    if (!hasGetter) {
                      descriptor.writable=true;
                    }
                  }
          
                  if (hasGetter) propertiesWithGetter.set(p, true);
          
                  // freeze the descriptor to avoid being modified by zone.js
                  // see https://github.com/angular/zone.js/blob/a5fe09b0fac27ac5df1fa746042f96f05ccb6a00/lib/browser/define-property.ts#L71
                  rawObjectDefineProperty(fakeWindow, p, Object.freeze(descriptor));
                }
              });
          
            return {
              fakeWindow,
              propertiesWithGetter,
            };
          }


          proxySandbox 由于是拷貝復制了一份 fakeWindow,不會污染全局 window,同時支持多個子應用同時加載。
          詳細源碼請查看
          :proxySandbox[14]

          二、Node.js中的沙箱實現

          VM

          VM是 Node.js 默認提供的一個內建模塊,VM 模塊提供了一系列 API 用于在 V8 虛擬機環境中編譯和運行代碼。JavaScript 代碼可以被編譯并立即運行,或編譯、保存然后再運行。

          const vm=require('vm');
          const script=new vm.Script('m + n'); // 先new一個腳本執行的容器實例
          const sandbox={ m: 1, n: 2 }; 
          const context=new vm.createContext(sandbox); // 實例化一個執行上下文
          const res=script.runInContext(context); // 運行
          console.log(res); // 打?。?

          執行上這的代碼就能拿到結果 3,同時,通過 vm.Script 還能指定代碼執行的「最大毫秒數」,超過指定的時長將終止執行并拋出一個異常:

          try {
            const script=new vm.Script('while(true){}',{ timeout: 50 });
            ....
          } catch (err){
            // 執行超過了50ms會打印超時的 log
            console.log(err.message);
          }

          上面的腳本執行將會失敗,被檢測到超時并拋出異常,然后被 Try Cache 捕獲到并打出 log,但同時需要注意的是 vm.Script 的 timeout 選項「只針對同步代有效」,而不包括是異步調用的時間,比如

            const script=new vm.Script('setTimeout(()=>{},2000)',{ timeout: 50 });

          上述代碼,并不是會在 50ms 后拋出異常,因為 50ms 上邊的代碼同步執行肯定完了,而 setTimeout 所用的時間并不算在內,也就是說 vm 模塊沒有辦法對異步代碼直接限制執行時間。我們也不能額外通過一個 timer 去檢查超時,因為檢查了執行中的 vm 也沒有方法去中止掉。

          另外,在 Node.js 通過 vm.runInContext 看起來似乎隔離了代碼執行環境,但實際上卻很容易「逃逸」出去。我們看下這個過程。

          使用VM模塊我們可以在獨立的環境中運行不受信任的代碼,這就意味著運行在沙箱里的代碼不能訪問Node進程了,對嗎?

          基本的使用示例代碼:

          "use strict";
          const vm=require("vm");
          const xyz=vm.runInNewContext(`let a="welcome!";a;`);
          console.log(xyz); // a

          現在我們嘗試訪問進程

          "use strict";
          const vm=require("vm");
          const xyz=vm.runInNewContext(`process`);
          console.log(xyz);


          “process is not defined”,所以默認情況下VM模塊不能訪問主進程,如果想要訪問需要指定授權。
          看起來默認不能訪問“process、require”等就滿足需求了,但是真的就沒有辦法觸及主進程并執行代碼了?
          看下面的例子:

          "use strict";
          const vm=require("vm");
          const xyz=vm.runInNewContext(`this.constructor.constructor('return this.process.env')()`);
          console.log(xyz);

          在javascript中this指向它所屬的對象,所以我們使用它時就已經指向了一個VM上下文之外的對象。那么訪問this的.constructor 就返回 Object Constructor ,訪問 Object Constructor 的 .constructor 返回 Function constructor 。
          Function constructor 就像javascript提供的最高函數,他可以訪問全局,所以他能返回全局事物。Function constructor允許從字符串生成函數,從而執行任意代碼。
          所以我們可以使用 Function constructor 返回主進程。關于 Function constructor 更多內容在
          這里[15]這里[16]。


          可以正常打印,也就是說順利拿到了主進程的process,也就是上面所說的產生了「逃逸」。這招同樣對突破Angular同樣有效 —— AngularJS 沙箱[17]。

          再看下面的例子:

          const vm=require('vm');
          const sandbox={};
          const script=new vm.Script('this.constructor.constructor("return process")().exit()');
          const context=vm.createContext(sandbox);
          script.runInContext(context);

          執行上邊的代碼,宿主程序立即就會「退出」,sandbox 是在 VM 之外的環境創建的,需 VM 中的代碼的 this 指向的也是 sandbox,那么

          //this.constructor 就是外所的 Object 構建函數
          const ObjConstructor=this.constructor; 
          //ObjConstructor 的 constructor 就是外包的 Function
          const Function=ObjConstructor.constructor;
          //創建一個函數,并執行它,返回全局 process 全局對象
          const process=(new Function('return process'))(); 
          //退出當前進程
          process.exit(); 

          沒有人愿意用戶一段腳本就能讓應用掛掉吧。除了退出進程序之外,實際上還能干更多的事情。
          有個簡單的方法就能避免通過 this.constructor 拿到 process,如下:

          const vm=require('vm');
          //創建一外無 proto 的空白對象作為 sandbox
          // const sandbox={}; // 能通過this.constructor 拿到 process
          const sandbox=Object.create(null); // 這樣就能防止this.constructor 拿到 process
          const script=new vm.Script('this.constructor.constructor("return process")()');
          const context=vm.createContext(sandbox);
          const nodeProcess=script.runInContext(context);
          console.log(nodeProcess);

          但還是有風險的,由于 JavaScript 本身的動態的特點,各種黑魔法防不勝防。事實 Node.js 的官方文檔中也提到「 不要把 VM 當做一個安全的沙箱,去執行任意非信任的代碼」。

          VM2

          在社區中有一些開源的模塊用于運行不信任代碼,例如 sandbox、vm2、jailed 等。相比較而言 vm2 對各方面做了更多的安全工作,相對安全些?!高@也是為什么imageCook采用了該沙箱模塊」

          從 vm2 的官方 README 中可以看到,它基于 Node.js 內建的 VM 模塊,來建立基礎的沙箱環境,然后同時使用上了文介紹過的 ES6 的 Proxy 技術來防止沙箱腳本逃逸。

          用同樣的測試代碼來試試 vm2:

          const { VM }=require('vm2');
          new VM().run('this.constructor.constructor("return process")().exit()');

          如上代碼,并沒有成功結束掉宿主程序,vm2 官方 REAME 中說「vm2 是一個沙盒,可以在 Node.js 中安全的執行不受信任的代碼」。


          然而,事實上我們還是可以干一些「壞」事情,比如:

          只要能干壞事情,就是不安全的

          const { VM }=require('vm2');
          const vm=new VM({ timeout: 1000, sandbox: {}});
          vm.run('new Promise(()=>{})');

          上邊的代碼將永遠不會執行結束,如同 Node.js 內建模塊一樣,vm2 的 timeout 對異步操作是無效的。同時,vm2 也不能額外通過一個 timer 去檢查超時,因為它也沒有辦法將執行中的 vm 終止掉。這會一點點耗費完服務器的資源,讓你的應用掛掉。

          那么或許你會想,我們能不能在上邊的 sandbox 中放一個假的 Promise 從而禁掉 Promise 呢?答案是能提供一個「假」的 Promise,但卻沒有辦法完成禁掉 Promise,比如

          const { VM }=require('vm2');
          const vm=new VM({ 
            timeout: 1000, 
            sandbox: { Promise: function(){}}
          });
          
          vm.run('Promise=(async function(){})().constructor;new Promise(()=>{});');

          可以看到通過一行 Promise=(async function(){})().constructor 就可以輕松再次拿到 Promise 了。從另一個層面來看,況且或許有時我們還想讓自定義腳本支持異步處理呢。

          關于VM2還有更多新的和創新性的繞過 ——更多逃逸[18]。

          除了從沙箱逃逸,還可以使用 infinite while loop 創建無限循環拒絕服務。

          const {VM}=require('vm2');
          
          new VM({timeout:1}).run(`
              function main(){
                  while(1){}
              }
              
              new Proxy({}, {
                  getPrototypeOf(t){
                      global.main();
                  }
              })`
          );

          Safeify[19]:Node.js環境下建立一個更安全的沙箱

          通過上文的探究,我們并沒有找到一個完美的方案在 Node.js 建立安全的隔離的沙箱。其中 vm2 做了不少處理,相對來講算是較安全的方案了,但問題也很明顯,比如異步不能檢查超時的問題以及和宿主程序在相同進程的問題。

          沒有進程隔離時,通過 VM 創建的 sanbox 大體是這樣的


          那么,我們是不是可以嘗試,將非受信代碼,通過 vm2 這個模塊隔離在一個獨立的進程中執行呢?然后,執行超時時,直接將隔離的進程干掉,但這里我們需要考慮如下幾個問題:

          ?通過進程池統一調度管理沙箱進程

          如果來一個執行任務,創建一個進程,用完銷毀,僅處理進程的開銷就已經稍大了,并且也不能不設限的開新進程和宿主應用搶資源,那么,需要建一個進程池:
          前提:所有任務到來會創建一個 Script 實例,先進入一個 pending 隊列,然后直接將 script 實例的 defer 對象返回,調用處就能 await 執行結果了
          然后:由 sandbox master 根據工程進程的空閑程序來調度執行,master 會將 script 的執行信息,包括重要的 ScriptId,發送給空閑的 worker,worker 執行完成后會將「結果 + script 信息」回傳給 master,master 通過 ScriptId 識別是哪個腳本執行完畢了,就是結果進行 resolve 或 reject 處理。

          這樣,通過「進程池」既能降低「進程來回創建和銷毀的開銷」,也能確保不過度搶占宿主資源;同時,在異步操作超時,還能將工程進程直接殺掉;同時,master 將發現一個工程進程掛掉,會立即創建替補進程。

          ?處理的數據和結果公開給沙箱的方法

          進程間如何通訊,需要「動態代碼」操作數據后可以直接序列化然后通過 IPC 發送給隔離 Sandbox 進程,執行結果一樣經過序列化通過 IPC 傳輸。

          其中,如果想公開一個方法給 sandbox,因為不在一個進程,并不能方便的將一個方案的引用傳遞給 sandbox。我們可以將宿主的方法,在傳遞給 sandbox worker 之類做一下處理,轉換為一個「描述對象」,包括了允許 sandbox 調用的方法信息,然后將信息,如同其它數據一樣發送給 worker 進程,worker 收到數據后,識別出「方法描述對象」,然后在 worker 進程中的 sandbox 對象上建立代理方法,代理方法同樣通過 IPC 和 master 通訊。

          ?針對沙箱進程進行 CPU 和內存配額限制

          在 Linux 平臺,通過 CGroups 對沙箱進程進行整體的 CPU 和內存等資源的配額限制,CGroups 是 Control Groups 的縮寫,是 Linux 內核提供的一種可以限制、記錄、隔離進程組(Process Groups)所使用的物理資源(如:CPU、Memory、IO 等等)的機制。最初由 Google 的工程師提出,后來被整合進 Linux 內核。CGroups 也是 LXC 為實現虛擬化所使用的資源管理手段,可以說沒有 CGroups 就沒有 LXC。

          最終,我們建立了一個大約這樣的「沙箱環境」


          如此這般處理起來是不是感覺很麻煩?但我們就有了一個更加安全一些的沙箱環境了,基于這些處理被封裝為一個獨立的模塊
          Safeify[20],在Github上已經開源。

          相較于內建的 VM 及常見的幾個沙箱模塊, Safeify 具有如下特點:

          ?為將要執行的動態代碼建立專門的進程池,與宿主應用程序分離在不同的進程中執行?支持配置沙箱進程池的最大進程數量?支持限定同步代碼的最大執行時間,同時也支持限定包括異步代碼在內的執行時間?支持限定沙箱進程池的整體的 CPU 資源配額(小數)?支持限定沙箱進程池的整體的最大的內存限制(單位 m)


          簡單介紹一下 Safeify 如何使用,通過如下命令安裝

          npm i safeify --save

          在應用中使用,還是比較簡單的,如下代碼(TypeScript 中類似)

          import { Safeify } from 'safeify';
          
          const safeVm=new Safeify({
            timeout: 50,          //超時時間,默認 50ms
            asyncTimeout: 500,    //包含異步操作的超時時間,默認 500ms
            quantity: 4,          //沙箱進程數量,默認同 CPU 核數
            memoryQuota: 500,     //沙箱最大能使用的內存(單位 m),默認 500m
            cpuQuota: 0.5,        //沙箱的 cpu 資源配額(百分比),默認 50%
          });
          
          const context={
            a: 1, 
            b: 2,
            add(a, b) {
              return a + b;
            }
          };
          
          const rs=await safeVm.run(`return add(a, b)`, context);
          console.log('result',rs);

          關于安全的問題,沒有最安全,只有更安全。Safeify 已在部分項目中使用,但自定義腳本的功能是往往僅針對內網用戶,有不少動態執行代碼的場景其實是可以避免的,繞不開或實在需要提供這個功能時,希望本文或 Safeify 能對大家有所幫助就行了。

          結論

          運行不信任的代碼是非常困難的,只依賴軟件模塊作為沙箱技術,防止不受信任代碼用于非正當用途是不得已的決定。這可能促使云上SAAS應用的不安全,因為通過逃逸出沙箱進程多個租戶間的數據可能被訪問(主進程數據獲?。?,這樣你就可能可以通過session,secret等來潛入其他租戶。一個更安全的選擇是依賴于硬件虛擬化,比如每個租戶代碼在獨立的docker容器或AWS Lambada Function 中執行會是更好的選擇。

          下面是Auth0如何處理沙箱問題:Sandboxing Node.js with CoreOS and Docker[21]。「下來可以再詳細研究下實現」

          三、看一個case

          imageCook的使用case


          目標:拿到用于前端頁面渲染的index.js + index.css

          基本思路:

          ?模板代碼生成代碼:https://github.com/imgcook-dsl/react-xt-standard/blob/master/src/index.js?基于Group/倉庫名可以拿到整個倉庫的所有代碼?gitlab的代碼拉取實現方式可以參考:針對字節現狀封裝的Gitlab API[22] 「使用了Node.js的混合流」?github的代碼拉取可以參考:https://www.npmjs.com/package/download-git-repo 曾被vue-cli 2.x[23]版本使用

          {
              "package.json": "xxx",
              "src/index.js": "yyy"
          }

          ?拿到執行函數字符串

          module.exports=function(schema, option) {
            let imgNumber=0;
          
            const {prettier}=option;
            ...
            };

          ?Node.js沙箱執行,得到上面函數返回的字符串

          import { Safeify } from 'safeify';
          import { getRepoProjectEntries } from 'byte-gitlab';
          
          const safeVm=new Safeify({
            timeout: 50,          // 超時時間,默認 50ms
            asyncTimeout: 500,    // 包含異步操作的超時時間,默認 500ms
            quantity: 4,          // 沙箱進程數量,默認同 CPU 核數
            memoryQuota: 500,     // 沙箱最大能使用的內存(單位 m),默認 500m
            cpuQuota: 0.5,        // 沙箱的 cpu 資源配額(百分比),默認 50%
          });
          
          const context={
             schema: {}, 
             option: {}
          };
          
          (async ()=> {
            const zipStream=await getRepoProjectEntries({
              group: 'mordor',
              project: 'lynx-standard',
              branch: 'master'
            });
            
            zipStream
              .pipe(async (contents: string, path: string)=> {
                  const rs=await safeVm.run(contents, context);
                  console.log('result', rs);
                  
                  return rs;
              })
              .pipe(this.emitDone())
              .once("done", done)
              .once("error", (err)=> {
                console.log("流執行出錯統一監控:".red, err);
              });
          })();

          ?返回給客戶端

          關于 CSS 隔離

          常見的有,不再贅述:?CSS Module?namespace?Dynamic StyleSheet?css in js?Shadow DOM


          引用鏈接

          [1] 沙箱(Sandbox): http://www.arkteam.net/?p=2967
          [2] JavaScript中constructor屬性:
          https://segmentfault.com/a/1190000013245739
          [3] class:
          https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/class
          [4] Class構造方法:
          https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes/constructor
          [5] Object.getPrototypeOf():
          https://www.axihe.com/api/js-es/ob-object/get-prototype-of.html
          [6] Reflect.getPrototypeOf():
          https://www.axihe.com/api/js-es/ob-reflect/get-prototype-of.html
          [7]
          proto: https://www.axihe.com/api/js-es/ob-object/proto.html
          [8] Object.prototype.isPrototypeOf(): https://www.axihe.com/api/js-es/ob-object/is-prototype-of.html
          [9] instanceof: https://www.axihe.com/api/js-es/ex-relational/instanceof.html
          [10] TypeError: https://www.axihe.com/api/js-es/ob-error/type-error.html
          [11] 專訪 Wind.js 作者老趙(上):緣由、思路及發展: https://www.infoq.cn/article/interview-jscex-author-part-1
          [12] (0, eval)(‘this’): https://www.cnblogs.com/qianlegeqian/p/3950044.html
          [13] qiankun: https://qiankun.umijs.org/zh/guide
          [14] :proxySandbox: https://link.segmentfault.com/?enc=Mb%2BNNJjUrmTA7g2uf%2FTgzQ%3D%3D.IHwAeHwf8%2FPDd3WJLo%2F4dCWf2md2lzw7s%2BIEdUcUHmX7xMSccEguXX%2BFQBtpgU8SHiyqxgnCi00SvzmT95eNTRD1XaOHjO5xokQrsy%2BHYtQ%3D
          [15] 這里: https://link.juejin.cn/?target=http%3A%2F%2Fdfkaye.github.io%2F2014%2F03%2F14%2Fjavascript-eval-and-function-constructor%2F
          [16] 這里: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fi0natan%2Fnodebestpractices%2Fissues%2F211
          [17] AngularJS 沙箱: https://link.juejin.cn/?target=https%3A%2F%2Fportswigger.net%2Fresearch%2Fdom-based-angularjs-sandbox-escapes
          [18] 更多逃逸: https://github.com/patriksimek/vm2/issues?q=is%3Aissue+author%3AXmiliaH+is%3Aclosed
          [19] Safeify: https://github.com/Houfeng/safeify
          [20] Safeify: https://github.com/Houfeng/safeify
          [21] Sandboxing Node.js with CoreOS and Docker: https://link.juejin.cn/?target=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Du81pS05W1JY
          [22] 針對字節現狀封裝的Gitlab API: https://code.byted.org/toutiao-fe/logic-monitor-server/blob/dev/init/app/service/gitlab.ts
          [23] 2.x: https://github.com/vuejs/vue-cli/blob/v2.9.3/package.json

          近,印度的喀拉拉邦發生了一件令人遺憾又憤怒的慘案——

          一只母象好奇地接近人類的村莊,吃掉了村民送給它的食物,

          但沒想到,食物中有一顆塞著爆竹的菠蘿

          爆竹爆開,炸傷了母象的舌頭和下巴。

          在傷痛的刺激下,母象沖出村莊,跑到附近的河流里。

          它終究沒有抵抗住傷痛的折磨,

          在救援機構抵達之后,

          在眾多村民,還有兩頭救援象的注視之下,

          這頭大象安靜又痛苦地離開了這個世界……

          在尸檢的時候,醫生發現,這只母象的肚子里還有一個尚未出生的寶寶……

          當地的林業官員莫漢·克里希南(Mohan Krishnan)見證了這一切之后,跟網友們分享了這痛苦的故事:

          “它毫無保留地信任著人類。當它口中的菠蘿爆炸時,它肯定怕極了,不是為了自己,而是擔心它腹中那個即將來到世界的孩子。它是那么善良,即使在極度的痛苦中,它穿過村莊的時候也沒有踐踏任何一座房屋,它沒有傷害任何人,甚至都沒有找那個給它菠蘿的人報仇。它最終躲在水里,可能是想阻止蒼蠅和蟲子飛進嘴里叮咬傷口,

          我們已經計劃好在晚上把它拉上來,但我覺得,它可能知道自己要死了,它沒有接受我們的救助,下午四點的時候,它默默倒在了水中。救援象們哭了,它們的眼淚掉進河里,河水也開始洶涌起來,仿佛是在抗議著人類的自私……

          我們把它送回了它成長的土地上,它躺在木柴上,我們把它火葬了。驗尸的醫生告訴我,它已經懷孕了,盡管他帶著口罩,看不清臉上的表情,但我知道,他跟我一樣悲傷。我們向它鞠躬,致以最誠摯的敬意,姐妹,對不起……”

          看到了這樣的慘劇,網友們的心中充滿了怒火與哀傷,

          他們畫了許多的畫,來痛斥那個冷血的人類,哀悼這只可憐的大象:

          “它們的生命同樣重要?!?/p>

          (圖片里是象媽媽和孩子的對話:

          “寶寶,他們給了我們一些吃的?!?/p>

          “人類真好。”)

          “愿上帝盡快懲罰那些人”

          “可怕”

          (畫中是媽媽和孩子的最后一次對話:

          “媽媽,我是不是永遠也見不到人類了?”

          “沒什么好遺憾的,寶貝。”)

          “善待動物一點都不會影響到人類……

          讓這些大象生活在和平當中吧,地球也是它們的家??!”

          “這就是說明人到底能有多殘忍的例子?!?/p>

          (它懷孕了,它餓了,

          它犯了個錯誤,相信了人類,

          再一次證明,人性不值得相信??!對不起)

          “它們會在某個神國嬉戲,但不會是喀拉拉邦?!?/p>

          “識字率不能反映人性,

          可恥的喀拉拉邦。”

          (我們信任你們,你們辜負了我們)

          作為“萬物之靈”的人類,

          為何有時總會作出如此殘忍的行為?

          無法想象這只大象在死前有多絕望,

          惟愿世間真的有天堂……

          ref:

          https://www.facebook.com/mohan.krishnan.1426?fref=search&__tn__=%2Cd%2CP-R&eid=ARCynyRfrMKK6jk1848y552CcrXZHn_JT-gmFrwOMwTiukVaRk_wZHdW8TWcT3eJKqT6-C-z50URMGMx

          https://www.dailymail.co.uk/news/article-8380727/Pregnant-elephant-killed-India-eating-pineapple-filled-firecracker.html

          https://www.republicworld.com/entertainment-news/whats-viral/netizens-share-art-to-express-anger-over-devastating-death-of-elephant.html

          --------------------

          lolozil:氣死我了氣死我了氣死我了有的人不配活著!心碎啊啊啊啊啊啊

          一根炸串串:太尼瑪惡毒了這些人

          洛北洛:母象去世前一定很絕望,它肚子里還有一只象寶寶啊

          一只雞翅喲:我資助了一頭叫Meakham在清邁的母象,今天才收到新一個月的反饋,看到這個新聞真的好絕望,覺得自己的力量太小了,世界上怎么會有這么殘忍的人啊

          OvO吃面的同學:惟愿人賤自有天收

          KSKSMoe:人心的惡毒程度經常超出想象,氣死我了!


          主站蜘蛛池模板: 99精品国产一区二区三区2021| 日本中文字幕在线视频一区| 一区二区在线免费视频| 国产成人一区二区精品非洲| 免费在线视频一区| 国产在线观看精品一区二区三区91 | 夜夜高潮夜夜爽夜夜爱爱一区| 国产精品一区二区在线观看| 亚洲国产精品一区二区久久hs| 国产精品美女一区二区三区| 国产精品成人免费一区二区 | 日本福利一区二区| 亚洲av无码一区二区三区乱子伦 | 日韩精品一区二区三区老鸭窝| 一区二区三区福利| 中文字幕在线一区二区三区| 亚洲综合av一区二区三区不卡| 国产福利电影一区二区三区,日韩伦理电影在线福 | 精品国产免费一区二区三区| www亚洲精品少妇裸乳一区二区 | 国产欧美色一区二区三区| 无码人妻久久久一区二区三区| 日韩视频一区二区在线观看 | 精品亚洲AV无码一区二区三区| 日韩精品午夜视频一区二区三区| 日韩精品一区二区三区中文精品| 日本人的色道www免费一区| 国产精品高清一区二区三区不卡| 中文字幕在线播放一区| 国产精品小黄鸭一区二区三区| 少妇精品久久久一区二区三区| 日韩一区二区三区视频| 亚洲午夜一区二区三区| 日韩AV无码一区二区三区不卡毛片 | 日韩精品一区二区三区在线观看l| 精品亚洲A∨无码一区二区三区| 精品人伦一区二区三区潘金莲| 国产亚洲综合精品一区二区三区 | 精品一区二区三区在线成人| 亚洲免费视频一区二区三区| 午夜AV内射一区二区三区红桃视|