Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 久久久久国产一级毛片高清版,国产亚洲美女精品久久久久,国产精品亚洲一区二区三区久久

          整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          宇宙廠:Vue3.0 為何用 Proxy 替代 de

          宇宙廠:Vue3.0 為何用 Proxy 替代 defineProperty?

          家好,很高興又見(jiàn)面了,我是"高級(jí)前端?進(jìn)階?",由我?guī)е蠹乙黄痍P(guān)注前端前沿、深入前端底層技術(shù),大家一起進(jìn)步,也歡迎大家關(guān)注、點(diǎn)贊、收藏、轉(zhuǎn)發(fā)!

          1. 什么是 Object.defineProperty

          1.1 Object.defineProperty 基本用法

          Object.defineProperty() 允許精確添加或修改對(duì)象屬性。通過(guò)賦值添加的普通屬性會(huì)在枚舉屬性時(shí)(例如 for...in、Object.keys() 等)出現(xiàn),值可以被更改,也可以被刪除。

          defineProperty() 方法允許更改額外細(xì)節(jié),以使其不同于默認(rèn)值。默認(rèn)情況下,使用 Object.defineProperty() 添加的屬性是不可寫、不可枚舉和不可配置的。此外,Object.defineProperty() 使用 [[DefineOwnProperty]] 內(nèi)部方法,而不是 [[Set]],因此即使屬性已經(jīng)存在也不會(huì)調(diào)用 setter。


          Object.defineProperty(obj, prop, descriptor)

          方法每一個(gè)參數(shù)定義如下:

          • obj:要定義屬性的對(duì)象。
          • prop:一個(gè)字符串或 Symbol,指定了要定義或修改的屬性鍵。
          • descriptor:要定義或修改的屬性的描述符,包括:configurable(如是否可刪除)、enumerable、writable、get、set 等等。

          下面示例使用 Object.defineProperty 進(jìn)行對(duì)象屬性定義:

          const obj={};
          // 1. 使用 null 原型:沒(méi)有繼承的屬性
          const descriptor=Object.create(null);
          descriptor.value="static";
          
          // 默認(rèn)情況下,不可枚舉、不可配置、不可寫
          // obj.key="static modified" 賦值后依然是 "static"
          Object.defineProperty(obj, "key", descriptor);
          
          // 2. 使用一個(gè)包含所有屬性的臨時(shí)對(duì)象字面量來(lái)明確其屬性
          Object.defineProperty(obj, "key2", {
            enumerable: false,
            configurable: false,
            writable: false,
            value: "static",
          });
          
          // 3. 重復(fù)利用同一對(duì)象
          function withValue(value) {
            const d=withValue.d ||
              (withValue.d={
                enumerable: false,
                writable: false,
                configurable: false,
                value,
              });
          
            // 避免重復(fù)賦值
            if (d.value !==value) d.value=value;
            return d;
          }
          // 然后
          Object.defineProperty(obj, "key", withValue("static"));
          
          // 如果 freeze 可用,防止添加或刪除對(duì)象原型屬性
          // (value、get、set、enumerable、writable、configurable)
          (Object.freeze || Object)(Object.prototype);

          1.2 Object.defineProperty 優(yōu)缺點(diǎn)

          Object.defineProperty 的主要優(yōu)點(diǎn)包括:

          • Object.defineProperty() 方法可以對(duì)屬性的行為方式進(jìn)行細(xì)粒度的控制
          • 允許設(shè)置只讀屬性,防止意外修改
          • 開(kāi)發(fā)者可以決定某個(gè)屬性是否在枚舉期間出現(xiàn),從而實(shí)現(xiàn)特定的功能
          • 允許開(kāi)發(fā)者使屬性不可刪除,從而保證核心屬性的安全。

          當(dāng)然,Object.defineProperty 也有不足之處,主要體現(xiàn)在:

          • Object.defineProperty() 不能很好地處理數(shù)組,因?yàn)闊o(wú)法捕獲修改索引值或長(zhǎng)度屬性以及動(dòng)態(tài)屬性(動(dòng)態(tài) getter 是一種沒(méi)有為 property 顯式定義 getter,而是在訪問(wèn)屬性時(shí)動(dòng)態(tài)創(chuàng)建的),詳情可以看這篇文章(https://vue3js.cn/interview/vue3/proxy.html#一、object-defineproperty)。同時(shí),也不支持嵌套對(duì)象,這意味著不會(huì)觀察到嵌套對(duì)象的任何更改

          下面是對(duì)象示例:

          const obj={
              foo: "foo",
              bar: "bar"
          }
          observe(obj)
          delete obj.foo // no ok
          obj.jar='xxx' // no ok

          下面是數(shù)組示例:

          const arrData=[1,2,3,4,5];
          arrData.forEach((val,index)=>{
              defineProperty(arrData,index,val)
          })
          arrData.push() // no ok
          arrData.pop()  // no ok
          arrDate[0]=99 // ok

          基于對(duì)象和數(shù)組的以上局限性,Vue2 增加了 set、delete API,并且對(duì)數(shù)組 api 方法進(jìn)行一個(gè)重寫。

          • Object.defineProperty() 的語(yǔ)法很冗長(zhǎng),可能會(huì)增加可讀性,影響代碼的模塊化和可重用性。

          而掌握 Object.defineProperty 的關(guān)鍵在于透徹理解屬性描述符的屬性。 例如,正確地將 writable 屬性設(shè)置為 false 可以確保屬性值在整個(gè)程序中保持不變,從而減少出現(xiàn)錯(cuò)誤的機(jī)會(huì)。

          比如下面的示例將 π 置為常量后將無(wú)法修改:

          let constantObj={};
          Object.defineProperty(constantObj, 'pi', {
            value: 3.14159,
            writable: false
          });
          
          console.log(constantObj.pi);
          // Outputs 3.14159
          constantObj.pi=3;
          // Attempting to change the value
          console.log(constantObj.pi);
          // Still outputs 3.14159

          注意:Vue 3 改用了 Proxy 。Proxy 可以攔截對(duì)象屬性讀取、賦值和刪除操作,從而能夠在屬性發(fā)生變化時(shí)觸發(fā)相應(yīng)的更新。對(duì)于數(shù)組,Proxy 可以攔截?cái)?shù)組的修改操作,比如: push、pop、splice 等,從而能夠在數(shù)組發(fā)生變化時(shí)觸發(fā)相應(yīng)的更新。


          此外,Proxy 還可以攔截對(duì)象的原型方法和構(gòu)造函數(shù)調(diào)用,從而可以對(duì)對(duì)象的所有操作進(jìn)行攔截和處理。

          2. 什么是 Proxy

          JavaScript 的 Proxy 對(duì)象是一項(xiàng)強(qiáng)大的功能,使開(kāi)發(fā)者能夠攔截和自定義對(duì)對(duì)象執(zhí)行的操作,例如:屬性查找、賦值、枚舉和函數(shù)調(diào)用。 這種多功能工具允許開(kāi)發(fā)人員創(chuàng)建更高效、更靈活的代碼,同時(shí)還提高代碼的可維護(hù)性。

          Proxy 遵循以下語(yǔ)法規(guī)范:

          const p=new Proxy(target, handler)
          • target:要使用 Proxy 包裝的目標(biāo)對(duì)象,可以是任何類型的對(duì)象,包括原生數(shù)組,函數(shù),甚至另一個(gè)代理。
          • handler:一個(gè)通常以函數(shù)作為屬性的對(duì)象,各屬性中的函數(shù)分別定義了在執(zhí)行各種操作時(shí)代理 p 的行為。

          值得一提的是,handler 對(duì)象是一個(gè)容納一批特定屬性的占位符對(duì)象,包含有 Proxy 的各個(gè)捕獲器(trap)。而且所有的捕捉器是可選的。如果沒(méi)有定義某個(gè)捕捉器,那么就會(huì)保留源對(duì)象的默認(rèn)行為。

          常見(jiàn)的捕獲器包括:

          • getPrototypeOf()
          • setPrototypeOf()
          • isExtensible()
          • preventExtensions()
          • getOwnPropertyDescriptor()
          • defineProperty()
          • has()
          • get()
          • set()
          • deleteProperty()
          • ownKeys()
          • apply()
          • construct()

          在以下例子中,使用了一個(gè)原生 JavaScript 對(duì)象,Proxy 會(huì)將所有應(yīng)用到它的操作轉(zhuǎn)發(fā)到這個(gè)對(duì)象上。

          let target={};
          let p=new Proxy(target, {});
          
          p.a=37;
          // 操作轉(zhuǎn)發(fā)到目標(biāo)
          
          console.log(target.a);
          // 37. 操作已經(jīng)被正確地轉(zhuǎn)發(fā)

          3.Proxy 與 Object.defineProperty 主要區(qū)別

          Proxy 與 Object.defineProperty 一個(gè)主要區(qū)別在于抽象級(jí)別。 Proxy 在對(duì)象周圍創(chuàng)建一個(gè)新層,可以 Hook 任何屬性,而無(wú)需預(yù)先顯式定義, 而 Object.defineProperty 直接修改對(duì)象并要求相關(guān)屬性在定義時(shí)就存在。

          此外,Proxies 涵蓋了廣泛的 property 操作,而 Object.defineProperty 則專注于 attribute 屬性操作。

          關(guān)于 Proxy 和 Object.defineProperty 還需要弄清楚一個(gè)常見(jiàn)錯(cuò)誤,即將 Object.defineProperty 用于復(fù)雜的動(dòng)態(tài)對(duì)象,期望新屬性的反應(yīng)行為可能會(huì)導(dǎo)致意外結(jié)果,因?yàn)?Object.defineProperty 只影響現(xiàn)有屬性。

          比如下面的示例:

          let object={};
          Object.defineProperty(object, 'property', {
              value: 42,
              writable: false
          });
          object.newProperty=100;
          console.log(object.newProperty);
          // 輸出: 100
          // newProperty 的行為不受 Object.defineProperty 控制

          實(shí)現(xiàn)新屬性反應(yīng)性(Reactivity)的正確方法是使用 Proxy,或者為每個(gè)新屬性動(dòng)態(tài)實(shí)現(xiàn) Object.defineProperty。比如下面的 Proxy 示例:

          let targetObject={message: 'Hello, world'};
          let handler={
              set: function(target, prop, value) {
                  if (prop==='newProperty') {
                      target[prop]=value * 2;
                  } else {
                      target[prop]=value;
                  }
              }
          };
          
          let proxy=new Proxy(targetObject, handler);
          proxy.newProperty=100;
          console.log(proxy.newProperty);
          // 輸入: 200

          4. 使用 Proxy 的場(chǎng)景

          4.1 驗(yàn)證對(duì)象屬性

          考慮創(chuàng)建一個(gè)需要具有某些有條件所需屬性的嚴(yán)格架構(gòu)的對(duì)象,可以通過(guò)使用代理包裝對(duì)象并在 set 中實(shí)施驗(yàn)證檢查來(lái)管理,從而確保只有有效數(shù)據(jù)進(jìn)入對(duì)象。

          比如下面的代碼示例使用 Proxy 實(shí)現(xiàn) set 方法,驗(yàn)證對(duì)象的屬性是否在指定的 schema 中:

          let schema={
              id: {
                  type: 'number',
                  required: true
              },
              comment: {
                  type: 'string',
                  required: false
              }
          };
          let handler={
              set: function (target, key, value) {
                  if (schema[key] && typeof value !==schema[key].type) {
                      throw new Error(`Type ${typeof value} is not assignable to type ${schema[key].type}`);
                  } else if (schema[key] && schema[key].required && value===undefined) {
                      throw new Error(`${key} is required.`);
                  }
                  target[key]=value;
                  return true;
              }
          };
          
          let movie=new Proxy({}, handler);

          4.2 對(duì)象級(jí)訪問(wèn)控制

          Proxy 對(duì)象可以有效控制對(duì)象屬性的訪問(wèn),通常可用于提供對(duì)象的只讀視圖或限制可訪問(wèn)的對(duì)象屬性的范圍。

          比如下面的代碼示例表示訪問(wèn) password 屬性后則會(huì)拋出錯(cuò)誤:

          let personDetails={
              firstName: 'John',
              lastName: 'Doe',
              password: '12345!'
          };
          
          let handler={
              get: function (target, prop) {
                  if (prop==='password') {
                      throw new Error('Access to password is denied');
                  }
                  return target[prop];
              }
          };
          let proxy=new Proxy(personDetails, handler);
          console.log(proxy.password);
          // 拋出錯(cuò)誤
          console.log(proxy.firstName);
          // 輸出: 'John'

          又或者下面的代碼示例在修改元素屬性之前做精確的控制,從而屬性相互覆蓋:

          function assignIfNotExists(target, source){
              for (let prop in source) {
                  if (!target.hasOwnProperty(prop)) {
                      target[prop]=source[prop];
                  }
              }
          }
          let data={username: 'Zach'};
          let userInput={username: 'JohnDoe', password: 'secret'};
          // Avoid overwriting 'username' in data object
          assignIfNotExist(data, userInput);

          4.3 數(shù)據(jù)綁定和觀察者

          Proxy 可以幫助構(gòu)建數(shù)據(jù)綁定解決方案,比如:當(dāng)應(yīng)用程序的狀態(tài)發(fā)生變化時(shí),開(kāi)發(fā)者可能希望跟蹤變化并做出響應(yīng),比如: Vue.js 就是一個(gè)很好的示例。

          let state={
              count: 0
          };
          let handler={
              set: function (target, property, value) {
                  target[property]=value;
                  console.log(`State has changed. New ${property}: ${value}`);
                  return true;
              }
          };
          let proxy=new Proxy(state, handler);
          proxy.count=2;
          // 輸出: State has changed. New count: 2

          以上代碼示例,JavaScript Proxy 提供了對(duì)對(duì)象交互的精確控制,從而實(shí)現(xiàn)復(fù)雜行為、驗(yàn)證、訪問(wèn)控制等等。 然而,由于 Proxy 的復(fù)雜性,考慮使用 Proxy 的開(kāi)銷也同樣重要。 因此,Proxy 的使用應(yīng)該針對(duì)特定的挑戰(zhàn),其獨(dú)特的功能可以顯著提高系統(tǒng)操作和可讀性。

          5.Proxy 與 Object.defineProperty 深入比較

          5.1 性能

          JavaScript Proxy 比 Object.defineProperty 消耗的時(shí)間稍多, Proxy 本質(zhì)上應(yīng)用了一個(gè)額外的抽象層(處理程序),從而可能會(huì)使操作比 Object.defineProperty 更慢。

          比如下面的代碼示例:

          let object={};
          Object.defineProperty(object, 'property', {
              value: 42,
              writable: false
          });

          object 中的 property 屬性值必須是常量,直接訪問(wèn) property 非常簡(jiǎn)單快捷。 而對(duì)于 Proxy 來(lái)說(shuō),在獲取對(duì)象值之前有一個(gè)額外的檢查和驗(yàn)證過(guò)程:

          let targetObject={property: 42};
          let handler={
              get: function(target, prop) {
                  return target[prop];
              }
          };
          let proxy=new Proxy(targetObject, handler);

          5.2 代碼復(fù)雜性和可讀性

          Object.defineProperty 重點(diǎn)關(guān)注屬性級(jí)別, 當(dāng)需要控制屬性是否可以修改、配置甚至枚舉時(shí)則是理想選擇,同時(shí) Object.defineProperty 的用法直接且有針對(duì)性的,使代碼更容易閱讀和理解。

          然而,Proxy 在提供更高級(jí)別的抽象方面表現(xiàn)出色。 Proxy 對(duì)象可以針對(duì)整個(gè)對(duì)象,而不僅僅是單個(gè)屬性,從而允許開(kāi)發(fā)人員以更高級(jí)的方式攔截和重新定義對(duì)象的默認(rèn)行為。

          然而,Proxy 中的處理程序可能會(huì)造成復(fù)雜性,因?yàn)榭偸切枰ㄟ^(guò)一個(gè)額外的中間層。 其他開(kāi)發(fā)者也需要對(duì) Proxy 概念有更多的了解才能輕松閱讀 Proxy 代碼。

          5.3 模塊化和可重用性

          在模塊化和可重用性方面,當(dāng)想要為更大范圍甚至整個(gè)應(yīng)用程序定義全局處理程序行為時(shí),Proxy 通常會(huì)發(fā)揮作用。 Proxy 通常提供一種極好的方法來(lái)將特定的控制行為封裝在單獨(dú)的處理程序中。 這樣,同一個(gè)處理程序可以與多個(gè)目標(biāo)對(duì)象重復(fù)使用。

          相反,Object.defineProperty 允許模塊化和保護(hù)單個(gè)對(duì)象屬性,對(duì)于以模塊化方式定義、保護(hù)或控制對(duì)象的屬性非常重要。

          Proxy 提供了更多的可能性,捕獲更多的動(dòng)作,并提供對(duì)對(duì)象的更多控制。 然而,它們也會(huì)帶來(lái)性能成本,需要了解它們的用法,并且可能會(huì)使調(diào)試變得復(fù)雜。

          另一方面,Object.defineProperty 雖然不如代理那么強(qiáng)大和靈活,但提供了一種簡(jiǎn)單、直接且易于調(diào)試的方法。

          6.Proxy 常見(jiàn)方法

          6.1 Proxy 轉(zhuǎn)為普通對(duì)象

          const proxy={"name":"高級(jí)前端進(jìn)階"}
          
          const proxyObj=new Proxy(proxy, {
            get: (target, prop)=> prop in target ? target[prop] : 37
          });
          
          console.log(proxyObj.a)
          // 輸出 37
          console.log(proxyObj.name)
          // 輸出 ` 高級(jí)前端進(jìn)階 `
          console.log(JSON.stringify(proxyObj))
          // 輸出 {"name":"晴天"}

          值得注意的是,使用 JSON.parse(JSON.stringify(proxyObj)) 方法會(huì)刪除任何不能字符串化的內(nèi)容,比如:類、函數(shù)、回調(diào)等。

          如果確實(shí)需要,可以考慮使用 Lodash 的 cloneDeep 函數(shù),該方法在將 Proxy 對(duì)象轉(zhuǎn)換為 POJO(The Plain Old JavaScript Object) 的同時(shí)保持對(duì)象結(jié)構(gòu)方面確實(shí)做得很好。

           convertProxyObjectToPojo(proxyObj) {
            return _.cloneDeep(proxyObj);
          }

          6.2 Proxy 監(jiān)聽(tīng)數(shù)組元素變化

          以下示例表示 Proxy 確實(shí)能監(jiān)聽(tīng)到數(shù)組元素的變更,這與 defineProperty 是有差別的,至于監(jiān)聽(tīng)嵌套對(duì)象屬性變化可以自行驗(yàn)證。

          function get(target, prop, receiver) {
            console.log('target:' + target);
            console.log('property:' + prop);
            return Reflect.get(target, prop, receiver);
          }
          
          var handler={
            'get': get
          };
          
          // 為數(shù)組添加 Proxy
          var proxy=new Proxy([1,2,3,4,5], handler );
          
          console.log('Result=> beep:' + proxy.beep );
          // target: 1,2,3,4,5
          // property: beep
          // Result=> beep: undefined
          console.log('Result=> -123:' + proxy[ -123] );
          // target: 1,2,3,4,5
          // property: -123
          // proxy:16 Result=> -123: undefined
          console.log(proxy.fill( 1) );
          // target: 1,2,3,4,5
          // property: fill
          // target: 1,2,3,4,5
          // property: length
          // Proxy(Array) {0: 1, 1: 1, 2: 1, 3: 1, 4: 1}
          console.log('Result=> 0:' + proxy[ 0 ] );
          // target: 1,1,1,1,1
          // property: 0
          // Result=> 0: 1
          var arr1=[10, 20, 30, 40, 50];
          Object.setPrototypeOf(arr1, proxy);
          console.log('Result=> beep:' + arr1.beep );
          console.log('Result=> -123:' + arr1[ -123 ] );
          console.log(arr1.fill( 100) );
          // 輸出 (5) [100, 100, 100, 100, 100]
          console.log('Result=> 0:' + arr1[ 0 ] );

          6.3 Proxy 監(jiān)聽(tīng)嵌套對(duì)象

          var validator={
            get(target, key) {
              if (typeof target[key]==='object' && target[key] !==null) {
                  // 如果是對(duì)象則繼續(xù)創(chuàng)建 Proxy
                return new Proxy(target[key], validator)
              } else {
                return target[key];
              }
            },
            set (target, key, value) {
              console.log(target);
              // 輸出 {salary: 8250, Proffesion: '.NET Developer'}
              console.log(key);
              // 輸出 salary
              console.log(value);
              // 輸出 foo
              return true
            }
          }
          var person={
                firstName: "alfred",
                lastName: "john",
                inner: {
                  salary: 8250,
                  Proffesion: ".NET Developer"
                }
          }
          var proxy=new Proxy(person, validator)
          proxy.inner.salary='foo'
          // 這一句代碼會(huì)先訪問(wèn) proxy.inner 屬性,發(fā)現(xiàn)是 Object
          // 然后會(huì)繼續(xù)訪問(wèn) salary 屬性

          參考資料

          https://borstch.com/blog/objects-in-javascript-properties-methods-and-prototypes

          https://borstch.com/blog/proxies-vs-objectdefineproperty-when-to-use-which

          https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

          https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

          https://blog.javascripttoday.com/blog/deep-dive-proxies-in-javascript/

          https://vue3js.cn/interview/vue3/proxy.html#二、proxy

          https://vue3js.cn/interview/vue3/proxy.html#一、object-defineproperty

          https://juejin.cn/post/7306783965532717108

          https://www.youtube.com/watch?app=desktop&v=_k3WiANNB4U

          https://segmentfault.com/q/1010000043053833

          https://www.30secondsofcode.org/js/s/dynamic-getter-setter-proxy/

          https://gist.github.com/kgryte/713ab40f36c128bc1d52

          https://stackoverflow.com/questions/41299642/how-to-use-javascript-proxy-for-nested-objects

          he Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc). - MDN

          前言

          前面兩篇文章我們介紹了在JS中對(duì)引用類型做非破壞性操作的一些方式,對(duì)前端程序員來(lái)說(shuō),一個(gè)非常重要的好處就是可以讓我們寫出正確的redux reducer。在調(diào)研reducer更新方式的過(guò)程中,我注意到了一個(gè)庫(kù),叫做ImmerJS,它允許我們用破壞性的語(yǔ)法來(lái)得到非破壞性的結(jié)果。由于它是基于Proxy的機(jī)制來(lái)實(shí)現(xiàn)的,這讓我對(duì)Proxy這個(gè)ES6的新特性產(chǎn)生了很大的興趣。另外Vue3的reactivity system也是基于Proxy的,如果你使用Vue那么了解Proxy的機(jī)制也是很有必要的。

          所以這兩天我就抽時(shí)間去學(xué)習(xí)了一下,這篇文章就給大家簡(jiǎn)單介紹一下Proxy。

          什么是Proxy?

          Proxy,也就是”代理“,在程序中的意思通常是在用戶和真正訪問(wèn)的對(duì)象之間加了一個(gè)中間層,用戶不再直接訪問(wèn)原始對(duì)象,而是通過(guò)Proxy來(lái)做一個(gè)中轉(zhuǎn)。比如我們最常見(jiàn)的關(guān)于代理的概念就是網(wǎng)絡(luò)代理,分為"正向代理"和"反向代理"。"反向代理"拿nginx來(lái)講,它就是在用戶(瀏覽器)和目標(biāo)(web server)之間的一個(gè)中間層,瀏覽器通過(guò)nginx(代理)才能訪問(wèn)到web server。通過(guò)代理可以實(shí)現(xiàn)訪問(wèn)控制、負(fù)載均衡等目標(biāo)。

          我們這里講的JavaScript的Proxy也是類似的概念,只不過(guò)它是在對(duì)象對(duì)象的訪問(wèn)之間的一層代理,應(yīng)用了Proxy之后,我們將通過(guò)Proxy來(lái)訪問(wèn)目標(biāo)對(duì)象。

          而Proxy會(huì)改變JavaScript中一些基礎(chǔ)操作的執(zhí)行路徑,如讀取屬性、寫入屬性、對(duì)象遍歷、函數(shù)調(diào)用等。這是一種強(qiáng)大的元編程(meta programming)機(jī)制,可以實(shí)現(xiàn)很多通過(guò)之前無(wú)法實(shí)現(xiàn)的功能特性。雖然Proxy并沒(méi)有引入新的語(yǔ)法,但由于它會(huì)修改JavaScript的一些底層代碼的執(zhí)行方式,所以它是無(wú)法被完全polyfill的。

          Proxy的幾個(gè)概念

          在你讀Proxy的相關(guān)文檔時(shí),有幾個(gè)概念會(huì)頻繁出現(xiàn),我們先對(duì)這些概念做一個(gè)簡(jiǎn)單介紹。

          • target - target指的就是我們的目標(biāo)對(duì)象,也就是被代理的對(duì)象
          • trap - trap可以理解為是一些預(yù)定義的觸發(fā)點(diǎn)以及定義在這些觸發(fā)點(diǎn)上的函數(shù),比如上面提到的讀取屬性、寫入屬性、函數(shù)調(diào)用等,如果我們?cè)谶@些觸發(fā)點(diǎn)上定義了函數(shù),那么在對(duì)Proxy執(zhí)行對(duì)應(yīng)操作的時(shí)候,我們定義的函數(shù)將會(huì)被調(diào)用
          • handler - handler是一個(gè)對(duì)象,它包裝了所有Proxy提供的trap函數(shù),可以理解為是trap的集合

          Proxy的創(chuàng)建

          Proxy的創(chuàng)建是比較簡(jiǎn)單的,參考以下代碼:

          // 我們的原始對(duì)象
          var target={
           x: 1,
          }
          // handler對(duì)象,里面定義了各種trap函數(shù)
          var handler={
           get: function(obj, prop) {
           return obj[prop]
           }
          }
          // 創(chuàng)建proxy,參數(shù)分別為target和handler,含義見(jiàn)上方解釋
          var proxy=new Proxy(target, handler)
          // 通過(guò)proxy訪問(wèn)target對(duì)象
          console.log(proxy.x) // 輸出:1
          

          在上面代碼中,我們通過(guò)new Proxy來(lái)創(chuàng)建proxy對(duì)象,新創(chuàng)建的proxy對(duì)象將代理我們對(duì)target對(duì)象的訪問(wèn)。當(dāng)我們?cè)L問(wèn)proxy.x的時(shí)候,我們定義的handler中的get函數(shù)(trap)將被調(diào)用。get接受到的參數(shù)分別為target對(duì)象和我們要訪問(wèn)的屬性名稱。而因?yàn)槲覀冎苯臃祷刂付▽傩缘闹担苑祷豴roxy.x的返回值就是target中對(duì)應(yīng)屬性的值。

          當(dāng)然這里我們可以寫任意的代碼,也可以返回任意的值。

          有哪些traps?

          上面我們介紹了Proxy的創(chuàng)建方式,實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的trap:get。所有的trap都是可選的,如果我們定義了trap,當(dāng)我們執(zhí)行特定操作的時(shí)候就會(huì)執(zhí)行我們的trap,如果沒(méi)有定義trap,默認(rèn)操作就是執(zhí)行將操作直接傳遞到目標(biāo)對(duì)象。

          接下來(lái)我們就來(lái)看一下JavaScript提供的traps都有哪些:

          • get - 當(dāng)我們獲取目標(biāo)對(duì)象屬性值的時(shí)候被調(diào)用,返回屬性的值
          • set - 和get對(duì)應(yīng),當(dāng)我們寫入目標(biāo)對(duì)象的屬性值時(shí)被調(diào)用,改寫屬性的值
          • deleteProperty - 當(dāng)我們刪除目標(biāo)對(duì)象的屬性值時(shí)被調(diào)用
          • has - 當(dāng)我們對(duì)目標(biāo)對(duì)象使用in操作符時(shí)被調(diào)用,返回指定的屬性是否存在
          • apply - 當(dāng)我們對(duì)目標(biāo)對(duì)象(這里是一個(gè)函數(shù))執(zhí)行函數(shù)調(diào)用時(shí)這個(gè)trap會(huì)被調(diào)用
          • construct - 當(dāng)我們對(duì)目標(biāo)對(duì)象(要求是一個(gè)函數(shù))執(zhí)行new操作符的時(shí)候被調(diào)用
          • ownKeys - 當(dāng)我們調(diào)用Object.keys的時(shí)候這個(gè)trap將會(huì)被執(zhí)行
          • getPrototypeOf - 當(dāng)我們對(duì)目標(biāo)對(duì)象調(diào)用Object.getPrototypeOf時(shí)這個(gè)trap將被執(zhí)行
          • setPrototypeOf - 當(dāng)我們對(duì)目標(biāo)對(duì)象調(diào)用Object.setPrototypeOf時(shí)這個(gè)trap將被執(zhí)行
          • isExtensible - 當(dāng)我們對(duì)目標(biāo)對(duì)象執(zhí)行Object.isExtensible時(shí)這個(gè)trap將被執(zhí)行
          • preventExtensions - 當(dāng)我們對(duì)目標(biāo)對(duì)象執(zhí)行Object.preventExtensions時(shí)這個(gè)trap將被執(zhí)行
          • getOwnPropertyDescriptor - 當(dāng)我們對(duì)目標(biāo)對(duì)象執(zhí)行Object.getOwnPropertyDescriptor時(shí)這個(gè)trap將被調(diào)用
          • defineProperty - 當(dāng)我們對(duì)目標(biāo)對(duì)象執(zhí)行Object.defineProperty時(shí)這個(gè)trap將被調(diào)用

          Reflect

          Reflect是一個(gè)對(duì)象,針對(duì)上面提到的每一個(gè)trap,它都定義了一個(gè)static方法。它主要用于方便我們寫trap函數(shù)的時(shí)候?qū)⒉僮鱾鬟f到目標(biāo)對(duì)象。比如:

          var target={
           x: 1,
          }
          var handler={
           get: function(...args) {
           	// 我們使用Reflect將操作直接傳遞到目標(biāo)對(duì)象
           return Reflect.get(...args)
           },
           set: function(...args) {
           return Reflect.set(...args)
           }
          }
          var proxy=new Proxy(target, handler)
          console.log(proxy.x) // 輸出:1
          

          由于每個(gè)trap都有一個(gè)對(duì)應(yīng)的Reflect方法,而且他們的參數(shù)都是一致的,所以當(dāng)我們需要在trap中將操作直接傳遞到目標(biāo)對(duì)象的時(shí)候使用Reflect中的方法是非常方便的。Reflect中的很多方法和Object中的一些方法都比較相似,但還是有一些小的差別,比如返回值的不同等。可以參考這個(gè)網(wǎng)址。

          和getter、setter的區(qū)別

          通過(guò)以上的描述,我們可以看到Proxy和getter、setter有點(diǎn)像。它們都可以做到當(dāng)我們?cè)L問(wèn)或改寫一個(gè)對(duì)象的屬性值時(shí)調(diào)用一個(gè)特定的函數(shù),但有以下區(qū)別:

          1. Proxy除了對(duì)屬性值的獲取和改寫還可以改變其他JavaScript基礎(chǔ)操作的執(zhí)行方式,也就是它的功能比getter、setter多很多
          2. getter、setter是定義在對(duì)象本身上的,而Proxy是定義在另一個(gè)對(duì)象(proxy)上的。這表示Proxy允許我們比較方便的修改一些我們不方便直接改變其定義方式的對(duì)象的執(zhí)行方式,比如一些第三方庫(kù)中定義的對(duì)象等。

          總結(jié)

          ES6的Proxy對(duì)很多人可能比較陌生,但其實(shí)它真的非常簡(jiǎn)單,以上介紹的基本就是它的全部?jī)?nèi)容了。雖然實(shí)現(xiàn)起來(lái)比較簡(jiǎn)單,但是它可以實(shí)現(xiàn)很多非常強(qiáng)大的功能,比如ImmerJS這個(gè)庫(kù),它提供了用破壞性的語(yǔ)法來(lái)實(shí)現(xiàn)非破壞性操作的方式,我們打算下篇文章來(lái)介紹它

          升Web應(yīng)用的性能從未像今天這樣刻不容緩。

          在線經(jīng)濟(jì)活動(dòng)的比例日益提高,就連發(fā)展中國(guó)家和地區(qū)的經(jīng)濟(jì)活動(dòng)都已經(jīng)有5%以上在線進(jìn)行了(相關(guān)數(shù)據(jù)請(qǐng)參考本文后面的資源)。在這個(gè)超級(jí)鏈接、隨時(shí)在線的現(xiàn)代世界,用戶的期望也遠(yuǎn)非昔日可比。如果你的網(wǎng)站不能馬上響應(yīng),你的應(yīng)用不能立即運(yùn)行,用戶轉(zhuǎn)身就會(huì)投奔你的競(jìng)爭(zhēng)對(duì)手。

          亞馬遜大約10年前的一項(xiàng)研究表明,頁(yè)面加載時(shí)間減少1/10秒,能夠使其營(yíng)收增長(zhǎng)1%。另一項(xiàng)近期的調(diào)查也顯示,一多半受訪站點(diǎn)所有者提到因?yàn)樽约簯?yīng)用的性能不佳導(dǎo)致了收入減少或者用戶流失。

          一個(gè)網(wǎng)站到底多快才行?頁(yè)面加載每花1秒鐘,就有大約4%的用戶走掉。排名最靠前的電商站點(diǎn)的首次交互時(shí)間為1至3秒,這個(gè)區(qū)間的轉(zhuǎn)換率最高。顯而易見(jiàn),Web應(yīng)用性能的重要性與日俱增。

          提升性能其實(shí)不難,難的是怎么看到結(jié)果。本文給出能夠提升大約10倍網(wǎng)站性能的10個(gè)建議供大家參考。如此全面地涵蓋各種性能優(yōu)化技術(shù),這還是頭一回,但這些建議可能需要NGINX的一點(diǎn)支持。除了性能,這些建議也會(huì)涉及提升安全性。

          1 建議一:使用反向代理服務(wù)器讓應(yīng)用更快更安全

          果你的Web應(yīng)用只跑在一臺(tái)機(jī)器上,那要提升其性能非常簡(jiǎn)單:換一臺(tái)更快的,多配幾個(gè)處理器,多加幾條內(nèi)存,磁盤陣列也要高速的。換了以后,這臺(tái)機(jī)器上跑的WordPress服務(wù)器、Node.js或Java應(yīng)用速度都會(huì)加快。(要是應(yīng)用還會(huì)訪問(wèn)另一臺(tái)數(shù)據(jù)庫(kù)服務(wù)器,那也簡(jiǎn)單:找兩臺(tái)更快的機(jī)器,用更快的網(wǎng)絡(luò)連起來(lái)就行了。)

          麻煩在于,機(jī)器速度并不是問(wèn)題。很多時(shí)候Web應(yīng)用慢,是因?yàn)橐诟鞣N任務(wù)之間切換,一會(huì)兒要處理數(shù)千個(gè)連接上的用戶請(qǐng)求,一會(huì)兒要向磁盤讀寫文件,一會(huì)兒又要運(yùn)行應(yīng)用的代碼,一會(huì)兒又要去干別的。應(yīng)用服務(wù)器因此可能出現(xiàn)各種狀況,耗盡內(nèi)存、交換文件,或者讓很多請(qǐng)求等待一個(gè)硬盤I/O之類的任務(wù)。

          除了升級(jí)硬件,其實(shí)你還可以選擇另外一種完全不同的方法:加一臺(tái)反向代理服務(wù)器,分擔(dān)上述一些任務(wù)。反向代理服務(wù)器位于運(yùn)行應(yīng)用的機(jī)器之前,負(fù)責(zé)處理來(lái)自外網(wǎng)的請(qǐng)求。反向代理服務(wù)器直接連到互聯(lián)網(wǎng),它與應(yīng)用服務(wù)器通信使用的是快速的內(nèi)部網(wǎng)絡(luò)。

          反向代理服務(wù)器可以讓應(yīng)用服務(wù)器專注于構(gòu)建頁(yè)面,然后交給反向代理向外網(wǎng)發(fā)送,而不必理會(huì)用戶與應(yīng)用的交互。由于不必等待客戶端的響應(yīng),應(yīng)用服務(wù)器的運(yùn)行速度能達(dá)到接近最優(yōu)的水平。

          增加反向代理服務(wù)器同時(shí)也可以為Web服務(wù)器增添靈活性。比如,假設(shè)執(zhí)行某種任務(wù)的服務(wù)器過(guò)載了,那隨時(shí)可以再增加一臺(tái)同類服務(wù)器;而如果這臺(tái)服務(wù)器掛了,替換它也很容易。

          鑒于這種靈活性,反向代理服務(wù)器往往也是其他性能優(yōu)化手段的先決條件,比如:

          • 負(fù)載均衡(參見(jiàn)“建議二”),反向代理服務(wù)器上運(yùn)行負(fù)載均衡服務(wù),把流量平均分配給幾臺(tái)應(yīng)用服務(wù)器。有了負(fù)載均衡,添加應(yīng)用服務(wù)器根本不需要修改應(yīng)用。
          • 緩存靜態(tài)文件(參見(jiàn)“建議三”),圖片或代碼之類的可以直接請(qǐng)求的文件,都可以保存在反向代理服務(wù)器中,以便直接發(fā)給客戶端。這樣不僅可以更快地響應(yīng)請(qǐng)求,還能減輕應(yīng)用服務(wù)器的負(fù)擔(dān),加快其運(yùn)行速度。
          • 保證站點(diǎn)安全,可以配置反向代理服務(wù)器提升其安全級(jí)別,通過(guò)它監(jiān)控來(lái)快速識(shí)別和響應(yīng)攻擊,從而保存應(yīng)用服務(wù)器安全。

          NGINX專門為使用反向代理服務(wù)器做了設(shè)計(jì),使其天然支持上述優(yōu)化。由于使用事件驅(qū)動(dòng)的處理機(jī)制,NGINX比傳統(tǒng)服務(wù)器效率更高。NGINX Plus則增加了更高端的反向代理功能,如應(yīng)用體檢、特有的請(qǐng)求路由、高級(jí)緩存和售后支持。

          傳統(tǒng)服務(wù)器與NGINX Worker的比較

          建議二:增加負(fù)載均衡服務(wù)器

          加負(fù)載均衡服務(wù)器相對(duì)簡(jiǎn)單,但卻能顯著提升站點(diǎn)性能和安全性。通過(guò)它把流量分配給多個(gè)服務(wù)器,就可以不必升級(jí)Web服務(wù)器了。就算應(yīng)用本身寫得不太好,或者難以擴(kuò)展,負(fù)載均衡都可以在不做其他改變的情況下提升用戶體驗(yàn)。

          負(fù)載均衡服務(wù)器首先是一個(gè)反向代理服務(wù)器(參見(jiàn)“建議一”),負(fù)責(zé)把來(lái)自互聯(lián)網(wǎng)的請(qǐng)求轉(zhuǎn)發(fā)給其他服務(wù)器。這里關(guān)鍵在于負(fù)載均衡服務(wù)器可以支持兩臺(tái)以上的應(yīng)用服務(wù)器,使用一種選擇算法在不同的服務(wù)器間分配請(qǐng)求。最簡(jiǎn)單的負(fù)載均衡算法是循環(huán)調(diào)度,即把新請(qǐng)求依次轉(zhuǎn)發(fā)給可用服務(wù)器中的下一臺(tái)服務(wù)器。其他算法還有把請(qǐng)求發(fā)給活動(dòng)連接最少的服務(wù)器。NGINX Plus支持一種功能,就是把用戶會(huì)話保持在同一臺(tái)服務(wù)器上,叫做會(huì)話保持。

          負(fù)載均衡服務(wù)器可以避免一臺(tái)服務(wù)器過(guò)載而其他服務(wù)器過(guò)閑,從而極大提升性能。同時(shí),有了它還可以讓W(xué)eb服務(wù)器擴(kuò)容更簡(jiǎn)單,因?yàn)榭梢赃x用比較便宜的服務(wù)器,同時(shí)保證物盡其用。

          可以通過(guò)負(fù)載均衡調(diào)度的協(xié)議包括HTTP、HTTPS、SPDY、HTTP/2、WebSocket、FastCGI、SCGI、uwsgi、memcached,以及其他一些應(yīng)用形式,包括基于TCP的應(yīng)用和其他第四層的協(xié)議。為此,首先要分析Web應(yīng)用,看性能短板在哪里,然后再確定使用哪一個(gè)。

          同一臺(tái)服務(wù)器或用于負(fù)載均衡的服務(wù)器也可以承擔(dān)其他任務(wù),比如SSL終止、視客戶端不同支持HTTP/1/x或HTTP/2、緩存靜態(tài)文件。

          NGINX經(jīng)常被用來(lái)做負(fù)載均衡,更多信息請(qǐng)參考我們以前發(fā)的介紹性文章、有關(guān)配置的文章、電子書(shū)和相關(guān)的在線視頻,當(dāng)然還有文檔。我們的商業(yè)版本NGINX Plus支持更多的負(fù)載均衡功能,如基于服務(wù)器響應(yīng)時(shí)間路由負(fù)載和支持微軟NTLM協(xié)議的負(fù)載均衡。

          建議三:緩存靜態(tài)及動(dòng)態(tài)內(nèi)容

          存能提升Web應(yīng)用性能,因?yàn)榭梢愿斓匕褍?nèi)容交付給客戶端。緩存的策略包括預(yù)處理內(nèi)容、在較快的設(shè)備上存儲(chǔ)內(nèi)容、把內(nèi)容保存在靠近客戶端的地方,以及同時(shí)運(yùn)用這些策略。

          緩存有兩種。

          • 靜態(tài)內(nèi)容緩存,不常變化的文件,如圖片(JPEG、PNG)和代碼(CSS、JavaScript),可以保存在邊緣服務(wù)器中,以便快速?gòu)膬?nèi)容或磁盤中獲取。
          • 動(dòng)態(tài)內(nèi)容緩存,很多Web應(yīng)用會(huì)為每個(gè)頁(yè)面請(qǐng)求生成全新的HTML,把生成的每個(gè)HTML都緩存一小段時(shí)間,可能顯著減少需要生成的頁(yè)面總數(shù),同時(shí)又可以保證交付的內(nèi)容足夠新鮮。

          假設(shè)一個(gè)頁(yè)面每秒被查看10次,而你緩存它1秒,那么90%針對(duì)這個(gè)頁(yè)面的請(qǐng)求都將來(lái)自在緩存。如果你單獨(dú)緩存靜態(tài)內(nèi)容,那么即使全新生成的頁(yè)面,很可能大部分都來(lái)自緩存的內(nèi)容。

          緩存Web應(yīng)用生成內(nèi)容的技術(shù)主要分三種。

          • 把內(nèi)容放到離用戶近的地方。離用戶近,傳輸時(shí)間少。
          • 把內(nèi)容放到較快的機(jī)器上。機(jī)器快,檢索速度快。
          • 把內(nèi)容從過(guò)度使用的機(jī)器中拿走。有時(shí)候機(jī)器會(huì)比在專注執(zhí)行特定任務(wù)時(shí)慢很多,那是因?yàn)樘嗳蝿?wù)讓它們分心。這時(shí)候把內(nèi)容拿到其他機(jī)器上,不僅對(duì)緩存的內(nèi)容有好處,對(duì)非緩存的內(nèi)容同樣有利,因?yàn)橥泄芩鼈兊闹鳈C(jī)的負(fù)擔(dān)減輕了。

          Web應(yīng)用的緩存可以在Web應(yīng)用服務(wù)器內(nèi)部或外部實(shí)現(xiàn)。首先,考慮緩存動(dòng)態(tài)內(nèi)容,以減輕應(yīng)用服務(wù)器的負(fù)載。其次,緩存用于靜態(tài)內(nèi)容(包括那些動(dòng)態(tài)生成內(nèi)容的臨時(shí)副本),進(jìn)一步減輕應(yīng)用服務(wù)器的負(fù)擔(dān)。然后,考慮把緩存轉(zhuǎn)移到其他更快或更靠近用戶的機(jī)器,給應(yīng)用服務(wù)器減負(fù),縮短傳輸時(shí)間。

          用好緩存能顯著加快應(yīng)用的響應(yīng)速度。對(duì)很多網(wǎng)頁(yè)來(lái)說(shuō),大圖片之類的靜態(tài)數(shù)據(jù),往往占據(jù)一半以上的內(nèi)容。不用緩存,查詢和傳輸這類數(shù)據(jù)可能會(huì)花好幾秒鐘,而用緩存,則可能只要花幾分之一秒。

          可以舉一個(gè)例子來(lái)說(shuō)明怎么使用緩存,NGINX和NGINX Plus通過(guò)兩個(gè)指令來(lái)設(shè)置緩存:proxy_cache_path和proxy_cache指定緩存的位置和大小、最長(zhǎng)緩存時(shí)間以及其他參數(shù)。使用第三個(gè)(也是很受歡迎的)指令proxy_cache_use_stale,甚至可以告訴緩存在本來(lái)應(yīng)該提供新鮮內(nèi)容的服務(wù)器太忙或宕機(jī)時(shí),提供原來(lái)的舊文件,對(duì)客戶端來(lái)說(shuō),拿到內(nèi)容總比拿不到強(qiáng)。從用戶角度看,這樣也可以樹(shù)立你的站點(diǎn)或應(yīng)用非常穩(wěn)定的形象。

          NGINX Plus支持高級(jí)緩存功能,包括緩存凈化(caching purging)和通過(guò)控制板以可視化的形式展示緩存狀態(tài),實(shí)現(xiàn)實(shí)時(shí)監(jiān)控。

          要了解NGINX中關(guān)于緩存的更多信息,可以看看參考文檔和NGINX Plus Admin Guide中的NGINX Content Caching。另外,關(guān)注微信公眾號(hào):Java技術(shù)棧,也可以獲取我整理的 NGINX 教程,都是干貨。

          注意: 緩存涉及開(kāi)發(fā)、決策和運(yùn)維,完善的緩存策略,比如本文提到的這些,能夠體現(xiàn)從DevOps角度考慮的價(jià)值。也說(shuō)是說(shuō),開(kāi)發(fā)人員、架構(gòu)師、運(yùn)維人員此時(shí)攜手,共同保障一個(gè)網(wǎng)站的功能、響應(yīng)時(shí)間、安全和業(yè)務(wù)目標(biāo)。


          建議四:壓縮數(shù)據(jù)

          縮同樣能極大提升性能。圖片、視頻、音樂(lè)等文件都有非常成熟和高效的壓縮標(biāo)準(zhǔn)(JPEG和PNG、MPEG-4、MP3),任何一個(gè)標(biāo)準(zhǔn)都可以把文件大小縮小一個(gè)數(shù)量級(jí)甚至更多。

          文本文件,包括HTML(純文本和HTML標(biāo)簽)、CSS和JavaScript代碼,經(jīng)常在不壓縮的情況下傳輸。壓縮這些數(shù)據(jù)對(duì)提升Web應(yīng)用的感知性能有時(shí)候特別明顯,尤其是移動(dòng)用戶的網(wǎng)絡(luò)很慢又不穩(wěn)定的情況下。

          因?yàn)槲谋緮?shù)據(jù)通過(guò)對(duì)于頁(yè)面交互能夠起到必要的支援作用,而多媒體數(shù)據(jù)則更多是錦上添花的作用。聰明的內(nèi)容壓縮可以把HTML、JavaScript、CSS等文本內(nèi)容的縮小30%以上,因此能夠相應(yīng)地減少加載時(shí)間。

          如果你使用SSL,壓縮又可以減少必須經(jīng)過(guò)SSL編碼的數(shù)據(jù)量,從而補(bǔ)償了壓縮這些數(shù)據(jù)的CPU時(shí)間。

          壓縮數(shù)據(jù)的方法非常多。比如,建議六中關(guān)于HTTP/2的部分就描述了一個(gè)新穎的壓縮思路,特別適合首部數(shù)據(jù)壓縮。還有一個(gè)關(guān)于文本壓縮的例子,就是可以在NGINX中開(kāi)啟GZIP壓縮。預(yù)壓縮文本數(shù)據(jù)之后,可以使用gzip_static指令直接發(fā)送.gz文件。

          建議五:優(yōu)化SSL/TLS

          來(lái)越多的網(wǎng)站在使用Secure Sockets Layer(SSL)及后來(lái)的Transport Layer Security(TLS)協(xié)議。SSL/TLS通過(guò)加密從源服務(wù)器發(fā)送給用戶的數(shù)據(jù)來(lái)提升網(wǎng)站安全性。Google會(huì)提升使用SSL/TLS的網(wǎng)站的搜索引擎排名,將有力地推動(dòng)這一進(jìn)程。點(diǎn)擊這里了解SSL/TLS運(yùn)行機(jī)制詳解。另外,關(guān)注微信公眾號(hào):Java技術(shù)棧,也可以獲取我整理的更多 HTTPS 教程,都是干貨。

          盡管采用率越來(lái)越高,但SSL/TLS造成的性能損失也困擾著很多網(wǎng)站。SSL/TLS拖慢網(wǎng)站的原因有兩個(gè)。

          1、每次打開(kāi)新連接的初次握手都必須創(chuàng)建加密密鑰,而瀏覽器使用HTTP/1.x對(duì)每個(gè)2、服務(wù)器建立多個(gè)連接的方式進(jìn)一步加劇了這個(gè)問(wèn)題。

          服務(wù)器端加密數(shù)據(jù)和客戶端解密數(shù)據(jù)的操作同樣也是開(kāi)銷。

          為了鼓勵(lì)人們使用SSL/TLS,HTTP/2和SPDY(參見(jiàn)建議六)的作者將這兩個(gè)協(xié)議設(shè)計(jì)為只讓瀏覽器針對(duì)一次會(huì)話建立一個(gè)連接。這樣就把SSL導(dǎo)致性能降低的兩個(gè)主要原因之一消滅掉了。然而,說(shuō)到優(yōu)化SSL/TLS性能,還是有很多事情可做。

          優(yōu)化SSL/TLS的方法因Web服務(wù)器而異。以NGINX為例,NGINX使用OpenSSL,運(yùn)行于普通機(jī)器上,能夠提供接近定制機(jī)器的性能。NGINX SSL performance詳細(xì)介紹了如何將SSL/TLS加密和解密的開(kāi)銷降至最低。

          此外,這里還有一篇文章,介紹了很多種提升SSL/TLS性能的方法。簡(jiǎn)單總結(jié)一下,涉及的技術(shù)主要有如下幾種。

          • 會(huì)話緩存。使用ssl_session_cache指令開(kāi)啟緩存,緩存每次SSL/STL連接時(shí)用到的參數(shù)。
          • 會(huì)話票或ID。把特定SSL/TLS會(huì)話的信息保存為一個(gè)會(huì)話票或ID,以便連接重用,而不必重新握手。
          • OCSP封套。通過(guò)緩存SSL/TLS證書(shū)信息減少握手時(shí)間。

          NGINX和NGINX Plus都可以來(lái)終止SSL/TLS,即處理客戶端信息的加密和解密,同時(shí)與其他服務(wù)器保持明文通信。在NGINX或NGINX Plus中設(shè)置處理SSL/TLS終止可以采取這幾個(gè)步驟。而對(duì)于在接受TCP連接的服務(wù)器上使用NGINX Plus而言,可以參考這里的設(shè)置步驟。

          建議六:實(shí)現(xiàn)HTTP/2或SPDY

          經(jīng)使用SSL/TLS的站點(diǎn),如果再使用HTTP/2或SPDY則很可能提升性能,因?yàn)橐粋€(gè)連接只要一次握手。尚未使用SSL/TLS、HTTP/2和SPDY的站點(diǎn)切換到SSL/TLS(通常會(huì)降低性能),從響應(yīng)速度方面看,可能是一次倒退。點(diǎn)擊這里了解HTTP/2詳解。

          谷歌2012年開(kāi)始SPDY項(xiàng)目,致力于在HTTP/1.x之上實(shí)現(xiàn)更快的速度。HTTP/2則是IETF最近批準(zhǔn)的基于SPDY的標(biāo)準(zhǔn)。SPDY得到了廣泛支持,但很快就將被HTTP/2取代。

          SPDY和HTTP/2的關(guān)鍵在于只用一個(gè)連接,而非多個(gè)連接。這一個(gè)連接是多路復(fù)用的,因此可以同時(shí)承載多個(gè)請(qǐng)求和響應(yīng)。

          只維持一個(gè)連接,可以省掉多個(gè)連接所需的設(shè)置和管理消耗。而且一個(gè)連接對(duì)SSL特別重要,因?yàn)榭梢詫SL/TLS建立安全連接所需的握手時(shí)間降至最少。

          SPDY協(xié)議要求使用SSL/TLS,HTTP/2并沒(méi)有正式要求,但目前所有支持HTTP/2的瀏覽器都只會(huì)在啟用SSL/TLS的情況下才會(huì)使用它。換句話說(shuō),支持HTTP/2的瀏覽器只有在網(wǎng)站使用SSL且服務(wù)器接受HTTP/2流量的情況下才會(huì)使用HTTP/2。否則,瀏覽器會(huì)基于HTTP/1.x通信。

          實(shí)現(xiàn)了SPDY或HTTP/2之后,域名分片、資源合并、圖片精靈等之前針對(duì)HTTP的性能優(yōu)化措施就用不著了。因此也可以簡(jiǎn)化代碼和部署。關(guān)于HTTP/2會(huì)帶來(lái)哪些變化,可以參考我們的這個(gè)白皮書(shū)。

          NGINX很早就開(kāi)始支持SPDY,而且今天使用SPDY的大多數(shù)站點(diǎn)都在運(yùn)行NGIN

          X。NGINX同樣率先支持了HTTP/2,2015年9月,NGINX開(kāi)源和NGINX Plus開(kāi)始支持 HTTP/2。

          隨著時(shí)間推移,NGINX希望大多數(shù)站點(diǎn)啟用SSL并遷移到HTTP/2。這樣不僅可以讓網(wǎng)站更安全,而且隨著新的優(yōu)化技術(shù)不斷涌現(xiàn),也可以通過(guò)簡(jiǎn)單的代碼實(shí)現(xiàn)更高的性能。

          建議七:升級(jí)軟件

          升應(yīng)用性能的一個(gè)簡(jiǎn)單的方法,就是根據(jù)可靠性及性能選擇軟件。此外,高質(zhì)量組件的開(kāi)發(fā)者更可能不斷提升性能和修復(fù)問(wèn)題,因此使用最新的穩(wěn)定版本是劃算。新發(fā)布的版本會(huì)得到開(kāi)發(fā)者和用戶更多的關(guān)注,同時(shí)也會(huì)利用新的編譯器優(yōu)化技術(shù),包括針對(duì)新硬件的調(diào)優(yōu)。

          相對(duì)舊版本,新發(fā)布的穩(wěn)定版本明顯性能更高。堅(jiān)持升級(jí),也可以保證在調(diào)優(yōu)、問(wèn)題修復(fù)和安全警報(bào)方面與時(shí)俱進(jìn)。

          不升級(jí)軟件也會(huì)妨礙利用新能力。比如,HTTP/2目前要求OpenSSL 1.0.1。從2016年下半年開(kāi)始,HTTP/2會(huì)要求OpenSSL 1.0.2,該版本發(fā)布于2015年1月。

          NGINX用戶可以從NGINX開(kāi)源軟件的最新版本或NGINX Plus開(kāi)始,它們支持套接字共享、線程池(參見(jiàn)下文),而且都會(huì)持續(xù)優(yōu)化性能。因此,檢查一下自己的軟件,盡量把它們升級(jí)到最新的版本。

          建議八:調(diào)優(yōu)Linux

          Linux是今天大多數(shù)Web服務(wù)器的底層操作系統(tǒng),作為一切基礎(chǔ)設(shè)施的基礎(chǔ),Linux對(duì)提升性能至關(guān)重要。默認(rèn)情況下,很多Linux系統(tǒng)都比較保守,僅以桌面辦公為需求,以占用少量資源為調(diào)優(yōu)目標(biāo)。對(duì)于Web應(yīng)用而言,為達(dá)到性能最佳,肯定需要重新調(diào)優(yōu)。

          Linux優(yōu)化因Web服務(wù)器而異。以NGINX為例,可以從以下幾方面考慮。另外,關(guān)注微信公眾號(hào):Java技術(shù)棧,也可以獲取我整理的 NGINX 教程,都是干貨。

          存量隊(duì)列。如果發(fā)現(xiàn)有一些連接得不到處理,可以增大net.core.somaxconn,即等待NGINX處理的最大連接數(shù)。如果這個(gè)連接數(shù)限制過(guò)小,應(yīng)該可以看到錯(cuò)誤消息,可以逐步提高這個(gè)值,直到錯(cuò)誤消息不再出現(xiàn)。

          • 文件描述符。NGINX對(duì)每個(gè)連接最多使用兩個(gè)文件描述符。如果系統(tǒng)服務(wù)于很多連接,可能需要增大sys.fs.file_max這個(gè)對(duì)描述符的系統(tǒng)級(jí)限制,以及nofile這個(gè)用戶文件描述符限制,以支持增大后的負(fù)載。
          • 臨時(shí)端口。在作為代理使用時(shí),NGINX會(huì)為每個(gè)上游服務(wù)器創(chuàng)建臨時(shí)端口。可以設(shè)置net.ipv4.ip_local_port_range,增大端口值的范圍,以增加可用的端口量。此外,還可以減小net.ipv4.tcp_fin_timeout的值,它控制非活動(dòng)端口釋放重用的等待時(shí)間,加快周轉(zhuǎn)。
          • 對(duì)NGINX而言,請(qǐng)參考NGINX性能調(diào)優(yōu)指南,了解如何不費(fèi)吹灰之力將你的Linux系統(tǒng)優(yōu)化為能夠支持更大的吞吐量。

          建議九:調(diào)優(yōu)Web服務(wù)器

          無(wú)論使用什么Web服務(wù)器,都需要針對(duì)應(yīng)用對(duì)其調(diào)優(yōu)。以下建議適用于任何Web服務(wù)器,但會(huì)給出只有NGINX的設(shè)置說(shuō)明。

          • 訪問(wèn)日志。不要每個(gè)請(qǐng)求的日志都馬上寫到磁盤,可以在內(nèi)存里做個(gè)緩存,然后批量定入。對(duì)NGINX而言,將buffer=_size_參數(shù)添加到access_log指令,等內(nèi)存緩沖區(qū)寫滿后再把日志寫到磁盤。如果你添加了**flush=_time_**參數(shù),那么緩沖區(qū)的內(nèi)容也會(huì)按照指定時(shí)間寫入磁盤。
          • 緩沖。緩沖用于在內(nèi)存里保存部分響應(yīng),直到緩沖區(qū)被填滿,可以實(shí)現(xiàn)對(duì)客戶端更有效的響應(yīng)。無(wú)法寫入內(nèi)存的響應(yīng)會(huì)被寫到磁盤,從而降低性能。在NGINX的緩沖啟用時(shí),可以使用proxy_buffer_size和proxy_buffers指令來(lái)管理它。
          • 客戶端活動(dòng)連接。活動(dòng)連接可以減少時(shí)間消耗,特別是在使用SSL/TLS的情下。對(duì)NGINX而言,可以針對(duì)客戶端提高keepalive_requests的數(shù)值,默認(rèn)值為100;也可以增大keepalive_timeout的值,讓活動(dòng)連接持續(xù)時(shí)間更長(zhǎng),從而讓后續(xù)請(qǐng)求得到更快響應(yīng)。
          • 上游活動(dòng)連接。上游連接,即連接到應(yīng)用服務(wù)器、數(shù)據(jù)庫(kù)服務(wù)器的連接,同樣可以從活動(dòng)連接的設(shè)置中獲得好處。對(duì)上游連接來(lái)說(shuō),可以增加活動(dòng)連接,也就是每個(gè)工作進(jìn)程可用的空閑活動(dòng)連接的數(shù)量。這樣可以增進(jìn)連接重用,減少重開(kāi)連接。關(guān)于活動(dòng)連接的更多信息,請(qǐng)參考這篇博客。
          • 限制。限制客戶端使用的資源可以提升性能和安全性。對(duì)NGINX而言,limit_conn和limit_conn_zone指令限制指定源的連接數(shù),而limit_rate限制帶寬。這些設(shè)置可以防止合法用戶“侵吞”資源,同時(shí)也有助于防止攻擊。limit_req和limit_req_zone指令限制客戶端請(qǐng)求。對(duì)于到上游服務(wù)器的連接,可以在上游配置區(qū)的服務(wù)器指令中使用max_conns參數(shù),它限制對(duì)上游服務(wù)器的連接,防止過(guò)載。相關(guān)的隊(duì)列指令會(huì)創(chuàng)建一個(gè)隊(duì)列,在max_conns限制到達(dá)后將指定的請(qǐng)求數(shù)保存指定的時(shí)間。
          • 工作進(jìn)程。工作進(jìn)程負(fù)責(zé)處理請(qǐng)求。NGINX采用基于事件的模型和OS相關(guān)的機(jī)制有效地在工作進(jìn)程間分配請(qǐng)求。建議將worker_processes的值設(shè)置為每個(gè)CPU一個(gè)工作進(jìn)程。如果需要,大多數(shù)系統(tǒng)都支持提高worker_connections的值(默認(rèn)為512)。可以通過(guò)試驗(yàn)找到最適合你系統(tǒng)的這個(gè)值。
          • 套接字分片。通常,一個(gè)套接字監(jiān)聽(tīng)器向所有工作進(jìn)程分發(fā)新連接。套按字分片則為每個(gè)工作進(jìn)程都創(chuàng)建一個(gè)套接字監(jiān)聽(tīng)器,由內(nèi)核在套接字監(jiān)聽(tīng)器可用時(shí)為其指定連接。這樣可以減少鎖爭(zhēng)用,提升多核系統(tǒng)上的性能。要啟用套接字分片,在listen指令中包含reuseport參數(shù)。
          • 線程池。一個(gè)費(fèi)時(shí)的操作會(huì)阻塞任何計(jì)算機(jī)進(jìn)程。對(duì)Web服務(wù)器軟件來(lái)說(shuō),磁盤訪問(wèn)可能阻礙很多較快的操作,比如內(nèi)存中的計(jì)算和復(fù)制。在使用線程池的情況下,慢操作會(huì)被指定給一組獨(dú)立的任務(wù),而主處理循環(huán)會(huì)繼續(xù)運(yùn)行較快的操作。磁盤操作完成后,結(jié)果會(huì)返回到主處理循環(huán)。在NGINX中,read()系統(tǒng)調(diào)用和sendfile()被轉(zhuǎn)載到了線程池。

          提示,修改任何操作系統(tǒng)及周邊設(shè)備的設(shè)置時(shí),每次只修改一項(xiàng),然后測(cè)試性能。如果該項(xiàng)修改導(dǎo)致了問(wèn)題,或者并未提升性能,再改回去。

          建議十:監(jiān)控實(shí)時(shí)動(dòng)態(tài)以發(fā)現(xiàn)問(wèn)題和瓶頸

          存應(yīng)用高性能的關(guān)鍵是實(shí)時(shí)監(jiān)控應(yīng)用性能。必須實(shí)時(shí)監(jiān)控特定設(shè)備及相應(yīng)Web基礎(chǔ)設(shè)施中應(yīng)用的動(dòng)態(tài)。

          監(jiān)控站點(diǎn)活動(dòng)多數(shù)情況下是被動(dòng)的,它只告訴你發(fā)生了什么,至于如何發(fā)現(xiàn)和解決問(wèn)題,則是你自己的事情。

          監(jiān)控可以捕獲以下幾種問(wèn)題:

          1、服務(wù)器停機(jī)

          2、服務(wù)器不穩(wěn),漏處理連接

          3、服務(wù)器出現(xiàn)大面積緩存失效

          4、服務(wù)器發(fā)送的內(nèi)容不對(duì)

          New Relic或Dynatrace等全局性的性能監(jiān)控工具,可以幫我們監(jiān)控遠(yuǎn)程加載頁(yè)面的時(shí)間,而NGINX則可以幫你監(jiān)控應(yīng)用交付這一端。應(yīng)用的性能數(shù)據(jù)可以告訴你優(yōu)化手段什么時(shí)候真正給用戶帶去了不同的體驗(yàn),以及什么時(shí)候需要擴(kuò)容以滿足越來(lái)越多的流量。

          為了幫助用戶盡快發(fā)現(xiàn)問(wèn)題,NGINX Plus增加了應(yīng)用程序體檢功能,會(huì)報(bào)告經(jīng)常重復(fù)出現(xiàn)的問(wèn)題。NGINX Plus還具備session draining特性,會(huì)在已有任務(wù)完成前阻止新連接,以及慢啟動(dòng)容量,從而讓恢復(fù)的服務(wù)器在負(fù)載均衡集群中達(dá)到應(yīng)有的速度。使用得當(dāng)?shù)那闆r下,健康體檢會(huì)在問(wèn)題顯著影響用戶體驗(yàn)之前幫你定位問(wèn)題,而session draining和慢啟動(dòng)則讓你替換服務(wù)器時(shí)不影響感知的性能和在線時(shí)間。這張圖展示了NGINX Plus內(nèi)置的實(shí)時(shí)活動(dòng)監(jiān)控的控制板,涵蓋了服務(wù)器、TCP連接和緩存。

          結(jié)論:10倍性能提升

          能提升因Web應(yīng)用不同會(huì)有巨大差異。實(shí)際的提升取決于預(yù)算、時(shí)間,以及現(xiàn)有實(shí)現(xiàn)的與理想性能的差距。那么怎么讓你的應(yīng)用獲得10倍的性能提升呢?

          為了幫大家理解每項(xiàng)優(yōu)化建議的潛能,下面再針對(duì)之前的建議給出一些實(shí)施方針,希望大家各取所需。

          • 反向代理服務(wù)器及負(fù)載均衡。沒(méi)有負(fù)載均衡或池負(fù)載均衡,可能導(dǎo)致極低的性能。添加一個(gè)反向代理服務(wù)器,比如NGINX,可以減少Web應(yīng)用在內(nèi)存和磁盤之間的往返。負(fù)載均衡可以把任務(wù)從過(guò)載的服務(wù)器轉(zhuǎn)移到空閑的服務(wù)器,也便于擴(kuò)展。這些改變能極大地提升性能,與原有的部署方式最差的時(shí)候相比,10倍性能提升是很輕松的事,即使不到10倍那也在總體上有了質(zhì)的飛躍。
          • 緩存動(dòng)態(tài)和靜態(tài)內(nèi)容。如果你的Web服務(wù)器同時(shí)又充當(dāng)了應(yīng)用服務(wù)器,那么通過(guò)緩存動(dòng)態(tài)內(nèi)容就可以達(dá)到高峰期10倍的性能提升。緩存靜態(tài)內(nèi)容也可以有幾倍的性能提升。
          • 壓縮數(shù)據(jù)。使用JPEG、PNG、MPEG-4以及MP3等壓縮格式能顯著提升性能。如果這些手段都用上了,那么壓縮的文本數(shù)據(jù)(代碼及HTML)可以將初始頁(yè)面加載時(shí)間提升兩倍。
          • 優(yōu)化SSL/TLS。安全握手對(duì)性能有很大影響,因此對(duì)其進(jìn)行優(yōu)化可以讓初次響應(yīng)加快兩倍,對(duì)于文本內(nèi)容較多的網(wǎng)站尤其如此。優(yōu)化SSL/TLS下的媒體文件帶來(lái)的性能提升很小。
          • 實(shí)施HTTP/2和SPDY。在使用SSL/TLS的情況下,這兩個(gè)協(xié)議有可能提升網(wǎng)站的整體性能。
          • 調(diào)優(yōu)Linux和Web服務(wù)器。使用優(yōu)化的緩沖策略、使用活動(dòng)連接,將耗時(shí)的任務(wù)轉(zhuǎn)載至獨(dú)立的線程池,可以顯著提升性能。比如線程池可以將磁盤操作密集性任務(wù)的性能提升至少一個(gè)數(shù)量級(jí)。

          希望大家自己多嘗試以上技術(shù),也希望大家分享自己在性能改進(jìn)方面的心得。

          譯者:為之漫筆

          來(lái)源:http://www.zcfy.cc/article/10-tips-for-10x-application-performance-nginx-22.html

          原文:https://www.nginx.com/blog/10-tips-for-10x-application-performance/


          主站蜘蛛池模板: 99精品一区二区三区无码吞精| 人成精品视频三区二区一区| 免费av一区二区三区| 欧美亚洲精品一区二区| 91福利国产在线观看一区二区| 亚洲精品色播一区二区| 麻豆文化传媒精品一区二区| 国产AⅤ精品一区二区三区久久 | 亚洲欧洲一区二区三区| 色窝窝免费一区二区三区| 91在线视频一区| 一区二区三区日韩精品| 国产熟女一区二区三区四区五区 | 国产微拍精品一区二区| 人妻AV中文字幕一区二区三区| 亚洲视频一区二区| 亚洲AV日韩精品一区二区三区| 国内精品视频一区二区三区| 亚洲.国产.欧美一区二区三区| 无码精品人妻一区二区三区中 | 狠狠综合久久av一区二区| 四虎成人精品一区二区免费网站| 精品亚洲AV无码一区二区三区 | 精品无码人妻一区二区三区品| 国产成人无码AV一区二区在线观看 | 性无码一区二区三区在线观看| 国产一区二区高清在线播放| 国产精品一区二区av不卡| 天码av无码一区二区三区四区 | 日本中文一区二区三区亚洲| 国产一区二区三区亚洲综合| 波多野结衣中文一区| 亚洲国产成人久久一区久久| 国产精品亚洲产品一区二区三区| 中文字幕在线播放一区| 久久久久人妻精品一区蜜桃| 亚洲美女视频一区| 日韩福利视频一区| 波多野结衣的AV一区二区三区| 国产精品熟女一区二区| 在线观看一区二区三区av|