<address id="ttjl9"></address>

      <noframes id="ttjl9"><address id="ttjl9"><nobr id="ttjl9"></nobr></address>
      <form id="ttjl9"></form>
        <em id="ttjl9"><span id="ttjl9"></span></em>
        <address id="ttjl9"></address>

          <noframes id="ttjl9"><form id="ttjl9"></form>

          首頁

          細數 TS 中那些奇怪的符號

          seo達人

          TypeScript 是一種由微軟開發的自由和開源的編程語言。它是 JavaScript 的一個超集,而且本質上向這個語言添加了可選的靜態類型和基于類的面向對象編程。


          本文阿寶哥將分享這些年在學習 TypeScript 過程中,遇到的 10 大 “奇怪” 的符號。其中有一些符號,阿寶哥第一次見的時候也覺得 “一臉懵逼”,希望本文對學習 TypeScript 的小伙伴能有一些幫助。


          好的,下面我們來開始介紹第一個符號 —— ! 非空斷言操作符。


          一、! 非空斷言操作符

          在上下文中當類型檢查器無法斷定類型時,一個新的后綴表達式操作符 ! 可以用于斷言操作對象是非 null 和非 undefined 類型。具體而言,x! 將從 x 值域中排除 null 和 undefined 。


          那么非空斷言操作符到底有什么用呢?下面我們先來看一下非空斷言操作符的一些使用場景。


          1.1 忽略 undefined 和 null 類型

          function myFunc(maybeString: string | undefined | null) { // Type 'string | null | undefined' is not assignable to type 'string'. // Type 'undefined' is not assignable to type 'string'.  const onlyString: string = maybeString; // Error const ignoreUndefinedAndNull: string = maybeString!; // Ok }

          1.2 調用函數時忽略 undefined 類型

          type NumGenerator = () => number; function myFunc(numGenerator: NumGenerator | undefined) { // Object is possibly 'undefined'.(2532) // Cannot invoke an object which is possibly 'undefined'.(2722) const num1 = numGenerator(); // Error const num2 = numGenerator!(); //OK }

          因為 ! 非空斷言操作符會從編譯生成的 JavaScript 代碼中移除,所以在實際使用的過程中,要特別注意。比如下面這個例子:


          const a: number | undefined = undefined; const b: number = a!; console.log(b);

          以上 TS 代碼會編譯生成以下 ES5 代碼:


          "use strict"; const a = undefined; const b = a; console.log(b);

          雖然在 TS 代碼中,我們使用了非空斷言,使得 const b: number = a!; 語句可以通過 TypeScript 類型檢查器的檢查。但在生成的 ES5 代碼中,! 非空斷言操作符被移除了,所以在瀏覽器中執行以上代碼,在控制臺會輸出 undefined。


          二、?. 運算符

          TypeScript 3.7 實現了呼聲最高的 ECMAScript 功能之一:可選鏈(Optional Chaining)。有了可選鏈后,我們編寫代碼時如果遇到 null 或 undefined 就可以立即停止某些表達式的運行??蛇x鏈的核心是新的 ?. 運算符,它支持以下語法:


          obj?.prop

          obj?.[expr]

          arr?.[index] func?.(args)

          這里我們來舉一個可選的屬性訪問的例子:


          const val = a?.b;

          為了更好的理解可選鏈,我們來看一下該 const val = a?.b 語句編譯生成的 ES5 代碼:


          var val = a === null || a === void 0 ? void 0 : a.b;

          上述的代碼會自動檢查對象 a 是否為 null 或 undefined,如果是的話就立即返回 undefined,這樣就可以立即停止某些表達式的運行。你可能已經想到可以使用 ?. 來替代很多使用 && 執行空檢查的代碼:


          if(a && a.b) { } if(a?.b){ } /**

          * if(a?.b){ } 編譯后的ES5代碼

          *

          * if(

          *  a === null || a === void 0

          *  ? void 0 : a.b) {

          * }

          */

          但需要注意的是,?. 與 && 運算符行為略有不同,&& 專門用于檢測 falsy 值,比如空字符串、0、NaN、null 和 false 等。而 ?. 只會驗證對象是否為 null 或 undefined,對于 0 或空字符串來說,并不會出現 “短路”。


          2.1 可選元素訪問

          可選鏈除了支持可選屬性的訪問之外,它還支持可選元素的訪問,它的行為類似于可選屬性的訪問,只是可選元素的訪問允許我們訪問非標識符的屬性,比如任意字符串、數字索引和 Symbol:


          function tryGetArrayElement<T>(arr?: T[], index: number = 0) { return arr?.[index];

          }

          以上代碼經過編譯后會生成以下 ES5 代碼:


          "use strict"; function tryGetArrayElement(arr, index) { if (index === void 0) { index = 0; } return arr === null || arr === void 0 ? void 0 : arr[index];

          }

          通過觀察生成的 ES5 代碼,很明顯在 tryGetArrayElement 方法中會自動檢測輸入參數 arr 的值是否為 null 或 undefined,從而保證了我們代碼的健壯性。


          2.2 可選鏈與函數調用

          當嘗試調用一個可能不存在的方法時也可以使用可選鏈。在實際開發過程中,這是很有用的。系統中某個方法不可用,有可能是由于版本不一致或者用戶設備兼容性問題導致的。函數調用時如果被調用的方法不存在,使用可選鏈可以使表達式自動返回 undefined 而不是拋出一個異常。


          可選調用使用起來也很簡單,比如:


          let result = obj.customMethod?.();

          該 TypeScript 代碼編譯生成的 ES5 代碼如下:


          var result = (_a = obj.customMethod) === null || _a === void 0 ? void 0 : _a.call(obj);

          另外在使用可選調用的時候,我們要注意以下兩個注意事項:


          如果存在一個屬性名且該屬性名對應的值不是函數類型,使用 ?. 仍然會產生一個 TypeError 異常。

          可選鏈的運算行為被局限在屬性的訪問、調用以及元素的訪問 —— 它不會沿伸到后續的表達式中,也就是說可選調用不會阻止 a?.b / someMethod() 表達式中的除法運算或 someMethod 的方法調用。

          三、?? 空值合并運算符

          在 TypeScript 3.7 版本中除了引入了前面介紹的可選鏈 ?. 之外,也引入了一個新的邏輯運算符 —— 空值合并運算符 ??。當左側操作數為 null 或 undefined 時,其返回右側的操作數,否則返回左側的操作數。


          與邏輯或 || 運算符不同,邏輯或會在左操作數為 falsy 值時返回右側操作數。也就是說,如果你使用 || 來為某些變量設置默認的值時,你可能會遇到意料之外的行為。比如為 falsy 值(''、NaN 或 0)時。


          這里來看一個具體的例子:


          const foo = null ?? 'default string'; console.log(foo); // 輸出:"default string" const baz = 0 ?? 42; console.log(baz); // 輸出:0

          以上 TS 代碼經過編譯后,會生成以下 ES5 代碼:


          "use strict"; var _a, _b; var foo = (_a = null) !== null && _a !== void 0 ? _a : 'default string';

          console.log(foo); // 輸出:"default string" var baz = (_b = 0) !== null && _b !== void 0 ? _b : 42;

          console.log(baz); // 輸出:0

          通過觀察以上代碼,我們更加直觀的了解到,空值合并運算符是如何解決前面 || 運算符存在的潛在問題。下面我們來介紹空值合并運算符的特性和使用時的一些注意事項。


          3.1 短路

          當空值合并運算符的左表達式不為 null 或 undefined 時,不會對右表達式進行求值。


          function A() { console.log('A was called'); return undefined;} function B() { console.log('B was called'); return false;} function C() { console.log('C was called'); return "foo";} console.log(A() ?? C()); console.log(B() ?? C());

          上述代碼運行后,控制臺會輸出以下結果:


          A was called

          C was called

          foo

          B was called

          false

          3.2 不能與 && 或 || 操作符共用

          若空值合并運算符 ?? 直接與 AND(&&)和 OR(||)操作符組合使用 ?? 是不行的。這種情況下會拋出 SyntaxError。


          // '||' and '??' operations cannot be mixed without parentheses.(5076) null || undefined ?? "foo"; // raises a SyntaxError // '&&' and '??' operations cannot be mixed without parentheses.(5076) true && undefined ?? "foo"; // raises a SyntaxError

          但當使用括號來顯式表明優先級時是可行的,比如:


          (null || undefined ) ?? "foo"; // 返回 "foo"

          3.3 與可選鏈操作符 ?. 的關系

          空值合并運算符針對 undefined 與 null 這兩個值,可選鏈式操作符 ?. 也是如此??蛇x鏈式操作符,對于訪問屬性可能為 undefined 與 null 的對象時非常有用。


          interface Customer {

           name: string;

           city?: string;

          } let customer: Customer = {

           name: "Semlinker" }; let customerCity = customer?.city ?? "Unknown city"; console.log(customerCity); // 輸出:Unknown city

          前面我們已經介紹了空值合并運算符的應用場景和使用時的一些注意事項,該運算符不僅可以在 TypeScript 3.7 以上版本中使用。當然你也可以在 JavaScript 的環境中使用它,但你需要借助 Babel,在 Babel 7.8.0 版本也開始支持空值合并運算符。


          四、?: 可選屬性

          在面向對象語言中,接口是一個很重要的概念,它是對行為的抽象,而具體如何行動需要由類去實現。 TypeScript 中的接口是一個非常靈活的概念,除了可用于對類的一部分行為進行抽象以外,也常用于對「對象的形狀(Shape)」進行描述。


          在 TypeScript 中使用 interface 關鍵字就可以聲明一個接口:


          interface Person {

           name: string;

           age: number;

          } let semlinker: Person = {

           name: "semlinker",

           age: 33,

          };

          在以上代碼中,我們聲明了 Person 接口,它包含了兩個必填的屬性 name 和 age。在初始化 Person 類型變量時,如果缺少某個屬性,TypeScript 編譯器就會提示相應的錯誤信息,比如:


          // Property 'age' is missing in type '{ name: string; }' but required in type 'Person'.(2741) let lolo: Person  = { // Error name: "lolo" }

          為了解決上述的問題,我們可以把某個屬性聲明為可選的:


          interface Person {

           name: string;

           age?: number;

          } let lolo: Person  = {

           name: "lolo" }

          4.1 工具類型

          4.1.1 Partial<T>

          在實際項目開發過程中,為了提高代碼復用率,我們可以利用 TypeScript 內置的工具類型 Partial<T> 來快速把某個接口類型中定義的屬性變成可選的:


          interface PullDownRefreshConfig {

           threshold: number;

           stop: number;

          } /**

          * type PullDownRefreshOptions = {

          *   threshold?: number | undefined;

          *   stop?: number | undefined;

          * }

          */ type PullDownRefreshOptions = Partial<PullDownRefreshConfig>

          是不是覺得 Partial<T> 很方便,下面讓我們來看一下它是如何實現的:


          /**

          * Make all properties in T optional

          */ type Partial<T> = {

           [P in keyof T]?: T[P];

          };

          4.1.2 Required<T>

          既然可以快速地把某個接口中定義的屬性全部聲明為可選,那能不能把所有的可選的屬性變成必選的呢?答案是可以的,針對這個需求,我們可以使用 Required<T> 工具類型,具體的使用方式如下:


          interface PullDownRefreshConfig {

           threshold: number;

           stop: number;

          } type PullDownRefreshOptions = Partial<PullDownRefreshConfig> /**

          * type PullDownRefresh = {

          *   threshold: number;

          *   stop: number;

          * }

          */ type PullDownRefresh = Required<Partial<PullDownRefreshConfig>>

          同樣,我們來看一下 Required<T> 工具類型是如何實現的:


          /**

          * Make all properties in T required

          */ type Required<T> = {

           [P in keyof T]-?: T[P];

          };

          原來在 Required<T> 工具類型內部,通過 -? 移除了可選屬性中的 ?,使得屬性從可選變為必選的。


          五、& 運算符

          在 TypeScript 中交叉類型是將多個類型合并為一個類型。通過 & 運算符可以將現有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性。


          type PartialPointX = { x: number; }; type Point = PartialPointX & { y: number; }; let point: Point = {

           x: 1,

           y: 1 }

          在上面代碼中我們先定義了 PartialPointX 類型,接著使用 & 運算符創建一個新的 Point 類型,表示一個含有 x 和 y 坐標的點,然后定義了一個 Point 類型的變量并初始化。


          5.1 同名基礎類型屬性的合并

          那么現在問題來了,假設在合并多個類型的過程中,剛好出現某些類型存在相同的成員,但對應的類型又不一致,比如:


          interface X {

           c: string;

           d: string;

          } interface Y {

           c: number;

           e: string } type XY = X & Y; type YX = Y & X; let p: XY; let q: YX;

          在上面的代碼中,接口 X 和接口 Y 都含有一個相同的成員 c,但它們的類型不一致。對于這種情況,此時 XY 類型或 YX 類型中成員 c 的類型是不是可以是 string 或 number 類型呢?比如下面的例子:


          p = { c: 6, d: "d", e: "e" };



          q = { c: "c", d: "d", e: "e" };



          為什么接口 X 和接口 Y 混入后,成員 c 的類型會變成 never 呢?這是因為混入后成員 c 的類型為 string & number,即成員 c 的類型既可以是 string 類型又可以是 number 類型。很明顯這種類型是不存在的,所以混入后成員 c 的類型為 never。


          5.2 同名非基礎類型屬性的合并

          在上面示例中,剛好接口 X 和接口 Y 中內部成員 c 的類型都是基本數據類型,那么如果是非基本數據類型的話,又會是什么情形。我們來看個具體的例子:


          interface D { d: boolean; } interface E { e: string; } interface F { f: number; } interface A { x: D; } interface B { x: E; } interface C { x: F; } type ABC = A & B & C; let abc: ABC = {

           x: {

             d: true,

             e: 'semlinker',

             f: 666 }

          }; console.log('abc:', abc);

          以上代碼成功運行后,控制臺會輸出以下結果:




          由上圖可知,在混入多個類型時,若存在相同的成員,且成員類型為非基本數據類型,那么是可以成功合并。


          六、| 分隔符

          在 TypeScript 中聯合類型(Union Types)表示取值可以為多種類型中的一種,聯合類型使用 | 分隔每個類型。聯合類型通常與 null 或 undefined 一起使用:


          const sayHello = (name: string | undefined) => { /* ... */ };

          以上示例中 name 的類型是 string | undefined 意味著可以將 string 或 undefined 的值傳遞給 sayHello 函數。


          sayHello("semlinker");

          sayHello(undefined);

          此外,對于聯合類型來說,你可能會遇到以下的用法:


          let num: 1 | 2 = 1; type EventNames = 'click' | 'scroll' | 'mousemove';

          示例中的 1、2 或 'click' 被稱為字面量類型,用來約束取值只能是某幾個值中的一個。


          6.1 類型保護

          當使用聯合類型時,我們必須盡量把當前值的類型收窄為當前值的實際類型,而類型保護就是實現類型收窄的一種手段。


          類型保護是可執行運行時檢查的一種表達式,用于確保該類型在一定的范圍內。換句話說,類型保護可以保證一個字符串是一個字符串,盡管它的值也可以是一個數字。類型保護與特性檢測并不是完全不同,其主要思想是嘗試檢測屬性、方法或原型,以確定如何處理值。


          目前主要有四種的方式來實現類型保護:


          6.1.1 in 關鍵字

          interface Admin {

           name: string;

           privileges: string[];

          } interface Employee {

           name: string;

           startDate: Date;

          } type UnknownEmployee = Employee | Admin; function printEmployeeInformation(emp: UnknownEmployee) { console.log("Name: " + emp.name); if ("privileges" in emp) { console.log("Privileges: " + emp.privileges);

           } if ("startDate" in emp) { console.log("Start Date: " + emp.startDate);

           }

          }

          6.1.2 typeof 關鍵字

          function padLeft(value: string, padding: string | number) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value;

           } if (typeof padding === "string") { return padding + value;

           } throw new Error(`Expected string or number, got '${padding}'.`);

          }

          typeof 類型保護只支持兩種形式:typeof v === "typename" 和 typeof v !== typename,"typename" 必須是 "number", "string", "boolean" 或 "symbol"。 但是 TypeScript 并不會阻止你與其它字符串比較,語言不會把那些表達式識別為類型保護。


          6.1.3 instanceof 關鍵字

          interface Padder {

           getPaddingString(): string;

          } class SpaceRepeatingPadder implements Padder { constructor(private numSpaces: number) {}

           getPaddingString() { return Array(this.numSpaces + 1).join(" ");

           }

          } class StringPadder implements Padder { constructor(private value: string) {}

           getPaddingString() { return this.value;

           }

          } let padder: Padder = new SpaceRepeatingPadder(6); if (padder instanceof SpaceRepeatingPadder) { // padder的類型收窄為 'SpaceRepeatingPadder' }

          6.1.4 自定義類型保護的類型謂詞(type predicate)

          function isNumber(x: any): x is number { return typeof x === "number";

          } function isString(x: any): x is string { return typeof x === "string";

          }

          七、_ 數字分隔符

          TypeScript 2.7 帶來了對數字分隔符的支持,正如數值分隔符 ECMAScript 提案中所概述的那樣。對于一個數字字面量,你現在可以通過把一個下劃線作為它們之間的分隔符來分組數字:


          const inhabitantsOfMunich = 1_464_301; const distanceEarthSunInKm = 149_600_000; const fileSystemPermission = 0b111_111_000; const bytes = 0b1111_10101011_11110000_00001101;

          分隔符不會改變數值字面量的值,但邏輯分組使人們更容易一眼就能讀懂數字。以上 TS 代碼經過編譯后,會生成以下 ES5 代碼:


          "use strict"; var inhabitantsOfMunich = 1464301; var distanceEarthSunInKm = 149600000; var fileSystemPermission = 504; var bytes = 262926349;

          7.1 使用限制

          雖然數字分隔符看起來很簡單,但在使用時還是有一些限制。比如你只能在兩個數字之間添加 _ 分隔符。以下的使用方式是非法的:


          // Numeric separators are not allowed here.(6188) 3_.141592 // Error 3._141592 // Error // Numeric separators are not allowed here.(6188) 1_e10 // Error 1e_10 // Error // Cannot find name '_126301'.(2304) _126301 // Error // Numeric separators are not allowed here.(6188) 126301_ // Error // Cannot find name 'b111111000'.(2304) // An identifier or keyword cannot immediately follow a numeric literal.(1351) 0_b111111000 // Error // Numeric separators are not allowed here.(6188) 0b_111111000 // Error

          當然你也不能連續使用多個 _ 分隔符,比如:


          // Multiple consecutive numeric separators are not permitted.(6189) 123__456 // Error

          7.2 解析分隔符

          此外,需要注意的是以下用于解析數字的函數是不支持分隔符:


          Number()

          parseInt()

          parseFloat()

          這里我們來看一下實際的例子:


          Number('123_456') NaN parseInt('123_456') 123 parseFloat('123_456') 123

          很明顯對于以上的結果不是我們所期望的,所以在處理分隔符時要特別注意。當然要解決上述問題,也很簡單只需要非數字的字符刪掉即可。這里我們來定義一個 removeNonDigits 的函數:


          const RE_NON_DIGIT = /[^0-9]/gu; function removeNonDigits(str) {

           str = str.replace(RE_NON_DIGIT, ''); return Number(str);

          }

          該函數通過調用字符串的 replace 方法來移除非數字的字符,具體的使用方式如下:


          removeNonDigits('123_456') 123456 removeNonDigits('149,600,000') 149600000 removeNonDigits('1,407,836') 1407836

          八、<Type> 語法

          8.1 TypeScript 斷言

          有時候你會遇到這樣的情況,你會比 TypeScript 更了解某個值的詳細信息。通常這會發生在你清楚地知道一個實體具有比它現有類型更確切的類型。


          通過類型斷言這種方式可以告訴編譯器,“相信我,我知道自己在干什么”。類型斷言好比其他語言里的類型轉換,但是不進行特殊的數據檢查和解構。它沒有運行時的影響,只是在編譯階段起作用。


          類型斷言有兩種形式:


          8.1.1 “尖括號” 語法

          let someValue: any = "this is a string"; let strLength: number = (<string>someValue).length;

          8.1.2 as 語法

          let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;

          8.2 TypeScript 泛型

          對于剛接觸 TypeScript 泛型的讀者來說,首次看到 <T> 語法會感到陌生。其實它沒有什么特別,就像傳遞參數一樣,我們傳遞了我們想要用于特定函數調用的類型。




          參考上面的圖片,當我們調用 identity<Number>(1) ,Number 類型就像參數 1 一樣,它將在出現 T 的任何位置填充該類型。圖中 <T> 內部的 T 被稱為類型變量,它是我們希望傳遞給 identity 函數的類型占位符,同時它被分配給 value 參數用來代替它的類型:此時 T 充當的是類型,而不是特定的 Number 類型。


          其中 T 代表 Type,在定義泛型時通常用作第一個類型變量名稱。但實際上 T 可以用任何有效名稱代替。除了 T 之外,以下是常見泛型變量代表的意思:


          K(Key):表示對象中的鍵類型;

          V(Value):表示對象中的值類型;

          E(Element):表示元素類型。

          其實并不是只能定義一個類型變量,我們可以引入希望定義的任何數量的類型變量。比如我們引入一個新的類型變量 U,用于擴展我們定義的 identity 函數:


          function identity <T, U>(value: T, message: U) : T { console.log(message); return value;

          } console.log(identity<Number, string>(68, "Semlinker"));



          除了為類型變量顯式設定值之外,一種更常見的做法是使編譯器自動選擇這些類型,從而使代碼更簡潔。我們可以完全省略尖括號,比如:


          function identity <T, U>(value: T, message: U) : T { console.log(message); return value;

          } console.log(identity(68, "Semlinker"));

          對于上述代碼,編譯器足夠聰明,能夠知道我們的參數類型,并將它們賦值給 T 和 U,而不需要開發人員顯式指定它們。


          九、@XXX 裝飾器

          9.1 裝飾器語法

          對于一些剛接觸 TypeScript 的小伙伴來說,在第一次看到 @Plugin({...}) 這種語法可能會覺得很驚訝。其實這是裝飾器的語法,裝飾器的本質是一個函數,通過裝飾器我們可以方便地定義與對象相關的元數據。


          @Plugin({

           pluginName: 'Device',

           plugin: 'cordova-plugin-device',

           pluginRef: 'device',

           repo: 'https://github.com/apache/cordova-plugin-device',

           platforms: ['Android', 'Browser', 'iOS', 'macOS', 'Windows'],

          }) @Injectable() export class Device extends IonicNativePlugin {}

          在以上代碼中,我們通過裝飾器來保存 ionic-native 插件的相關元信息,而 @Plugin({...}) 中的 @ 符號只是語法糖,為什么說是語法糖呢?這里我們來看一下編譯生成的 ES5 代碼:


          var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r;

          }; var Device = /** @class */ (function (_super) {

             __extends(Device, _super); function Device() { return _super !== null && _super.apply(this, arguments) || this;

             }

             Device = __decorate([

                 Plugin({ pluginName: 'Device', plugin: 'cordova-plugin-device', pluginRef: 'device', repo: 'https://github.com/apache/cordova-plugin-device', platforms: ['Android', 'Browser', 'iOS', 'macOS', 'Windows'],

                 }),

                 Injectable()

             ], Device); return Device;

          }(IonicNativePlugin));

          通過生成的代碼可知,@Plugin({...}) 和 @Injectable() 最終會被轉換成普通的方法調用,它們的調用結果最終會以數組的形式作為參數傳遞給 __decorate 函數,而在 __decorate 函數內部會以 Device 類作為參數調用各自的類型裝飾器,從而擴展對應的功能。


          9.2 裝飾器的分類

          在 TypeScript 中裝飾器分為類裝飾器、屬性裝飾器、方法裝飾器和參數裝飾器四大類。


          9.2.1 類裝飾器

          類裝飾器聲明:


          declare type ClassDecorator = <TFunction extends Function>(

           target: TFunction

          ) => TFunction | void;

          類裝飾器顧名思義,就是用來裝飾類的。它接收一個參數:


          target: TFunction - 被裝飾的類

          看完第一眼后,是不是感覺都不好了。沒事,我們馬上來個例子:


          function Greeter(target: Function): void {

           target.prototype.greet = function (): void { console.log("Hello Semlinker!");

           };

          } @Greeter class Greeting { constructor() { // 內部實現 }

          } let myGreeting = new Greeting();

          myGreeting.greet(); // console output: 'Hello Semlinker!';

          上面的例子中,我們定義了 Greeter 類裝飾器,同時我們使用了 @Greeter 語法糖,來使用裝飾器。


          友情提示:讀者可以直接復制上面的代碼,在 TypeScript Playground 中運行查看結果。

          9.2.2 屬性裝飾器

          屬性裝飾器聲明:


          declare type PropertyDecorator = (target:Object,

           propertyKey: string | symbol ) => void;

          屬性裝飾器顧名思義,用來裝飾類的屬性。它接收兩個參數:


          target: Object - 被裝飾的類

          propertyKey: string | symbol - 被裝飾類的屬性名

          趁熱打鐵,馬上來個例子熱熱身:


          function logProperty(target: any, key: string) { delete target[key]; const backingField = "_" + key; Object.defineProperty(target, backingField, {

             writable: true,

             enumerable: true,

             configurable: true }); // property getter const getter = function (this: any) { const currVal = this[backingField]; console.log(`Get: ${key} => ${currVal}`); return currVal;

           }; // property setter const setter = function (this: any, newVal: any) { console.log(`Set: ${key} => ${newVal}`); this[backingField] = newVal;

           }; // Create new property with getter and setter Object.defineProperty(target, key, { get: getter, set: setter,

             enumerable: true,

             configurable: true });

          } class Person { @logProperty public name: string; constructor(name : string) { this.name = name;

           }

          } const p1 = new Person("semlinker");

          p1.name = "kakuqo";

          以上代碼我們定義了一個 logProperty 函數,來跟蹤用戶對屬性的操作,當代碼成功運行后,在控制臺會輸出以下結果:


          Set: name => semlinker Set: name => kakuqo

          9.2.3 方法裝飾器

          方法裝飾器聲明:


          declare type MethodDecorator = <T>(target:Object, propertyKey: string | symbol,          

           descriptor: TypePropertyDescript<T>) => TypedPropertyDescriptor<T> | void;

          方法裝飾器顧名思義,用來裝飾類的方法。它接收三個參數:


          target: Object - 被裝飾的類

          propertyKey: string | symbol - 方法名

          descriptor: TypePropertyDescript - 屬性描述符

          廢話不多說,直接上例子:


          function LogOutput(tarage: Function, key: string, descriptor: any) { let originalMethod = descriptor.value; let newMethod = function(...args: any[]): any { let result: any = originalMethod.apply(this, args); if(!this.loggedOutput) { this.loggedOutput = new Array<any>();

             } this.loggedOutput.push({

               method: key,

               parameters: args,

               output: result,

               timestamp: new Date()

             }); return result;

           };

           descriptor.value = newMethod;

          } class Calculator { @LogOutput double (num: number): number { return num * 2;

           }

          } let calc = new Calculator();

          calc.double(11); // console ouput: [{method: "double", output: 22, ...}] console.log(calc.loggedOutput);

          9.2.4 參數裝飾器

          參數裝飾器聲明:


          declare type ParameterDecorator = (target: Object, propertyKey: string | symbol,

           parameterIndex: number ) => void

          參數裝飾器顧名思義,是用來裝飾函數參數,它接收三個參數:


          target: Object - 被裝飾的類

          propertyKey: string | symbol - 方法名

          parameterIndex: number - 方法中參數的索引值

          function Log(target: Function, key: string, parameterIndex: number) { let functionLogged = key || target.prototype.constructor.name; console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has

             been decorated`);

          } class Greeter {

           greeting: string; constructor(@Log phrase: string) { this.greeting = phrase;

           }

          } // console output: The parameter in position 0  // at Greeter has been decorated

          十、#XXX 私有字段

          在 TypeScript 3.8 版本就開始支持 ECMAScript 私有字段,使用方式如下:


          class Person {

           #name: string; constructor(name: string) { this.#name = name;

           }


           greet() { console.log(`Hello, my name is ${this.#name}!`);

           }

          } let semlinker = new Person("Semlinker");


          semlinker.#name; //     ~~~~~ // Property '#name' is not accessible outside class 'Person' // because it has a private identifier.

          與常規屬性(甚至使用 private 修飾符聲明的屬性)不同,私有字段要牢記以下規則:


          私有字段以 # 字符開頭,有時我們稱之為私有名稱;

          每個私有字段名稱都唯一地限定于其包含的類;

          不能在私有字段上使用 TypeScript 可訪問性修飾符(如 public 或 private);

          私有字段不能在包含的類之外訪問,甚至不能被檢測到。

          10.1 私有字段與 private 的區別

          說到這里使用 # 定義的私有字段與 private 修飾符定義字段有什么區別呢?現在我們先來看一個 private 的示例:


          class Person { constructor(private name: string){}

          } let person = new Person("Semlinker"); console.log(person.name);

          在上面代碼中,我們創建了一個 Person 類,該類中使用 private 修飾符定義了一個私有屬性 name,接著使用該類創建一個 person 對象,然后通過 person.name 來訪問 person 對象的私有屬性,這時 TypeScript 編譯器會提示以下異常:


          Property 'name' is private and only accessible within class 'Person'.(2341)

          那如何解決這個異常呢?當然你可以使用類型斷言把 person 轉為 any 類型:


          console.log((person as any).name);

          通過這種方式雖然解決了 TypeScript 編譯器的異常提示,但是在運行時我們還是可以訪問到 Person 類內部的私有屬性,為什么會這樣呢?我們來看一下編譯生成的 ES5 代碼,也許你就知道答案了:


          var Person = /** @class */ (function () { function Person(name) { this.name = name;

             } return Person;

          }()); var person = new Person("Semlinker"); console.log(person.name);

          這時相信有些小伙伴會好奇,在 TypeScript 3.8 以上版本通過 # 號定義的私有字段編譯后會生成什么代碼:


          class Person {

           #name: string; constructor(name: string) { this.#name = name;

           }


           greet() { console.log(`Hello, my name is ${this.#name}!`);

           }

          }

          以上代碼目標設置為 ES2015,會編譯生成以下代碼:


          "use strict"; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet)

           || function (receiver, privateMap, value) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to set private field on non-instance");

             }

             privateMap.set(receiver, value); return value;

          }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet)

           || function (receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to get private field on non-instance");

             } return privateMap.get(receiver);

          }; var _name; class Person { constructor(name) {

               _name.set(this, void 0);

               __classPrivateFieldSet(this, _name, name);

             }

             greet() { console.log(`Hello, my name is ${__classPrivateFieldGet(this, _name)}!`);

             }

          }

          _name = new WeakMap();

          通過觀察上述代碼,使用 # 號定義的 ECMAScript 私有字段,會通過 WeakMap 對象來存儲,同時編譯器會生成 __classPrivateFieldSet 和 __classPrivateFieldGet 這兩個方法用于設置值和獲取值。

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          8個JavaScript庫可更好地處理本地存儲

          seo達人

          Local Storage Bridge

          https://github.com/krasimir/l...

          如果你必須在同一個瀏覽器中從一個標簽頁發送消息到另一個標簽頁,你不必用艱難的方式。Local storage bridge在這里讓任務變得更簡單。

          基本使用:

          // 發送 lsbridge.send(‘app.message.error’, { error: ‘Out of memory’ });

          // 監聽 lsbridge.subscribe(‘app.message.error’, function(data) { console.log(data); // { error: ‘Out of memory’ } });

          Basil.js

          image

          Basil.js統一了session、localStorage和cookie,為你提供了一種處理數據的直接方法。

          基本使用:

          let basil = new Basil(options);
          
          basil.set(‘name’, ‘Amy’);
          basil.get(‘name’);
          basil.remove(‘name’);
          basil.reset();

          store.js

          https://github.com/marcuswest...

          Store.js像其他東西一樣處理數據存儲。但還有更多的功能,它的一個高級特性是讓你更深入地訪問瀏覽器支持。

          基本使用:

          store.set(‘book’, { title: ‘JavaScript’ }); // Store a book store.get(‘book’);

          // Get stored book store.remove(‘book’); // Remove stored book store.clearAll(); // Clear all keys

          lscache

          https://github.com/pamelafox/...

          它與localStorage API類似。事實上,它是localStorage的一個封裝器,并使用HTML5模擬memcaches函數。在上面的文檔中發現更多的功能。

          基本使用:

          lscache.set(‘name’, ‘Amy’, 5); // 數據將在5分鐘后過期 lscache.get(‘name’);

          Lockr

          image

          Lockr建立在localStorage API之上。它提供了一些有用的方法來更輕松地處理本地數據。

          是什么讓你要使用此庫而不是localStorage API?

          好吧,localStorage API僅允許你存儲字符串。如果要存儲數字,則需要先將該數字轉換為字符串。在Lockr中不會發生這種情況,因為Lockr允許你存儲更多的數據類型甚至對象。

          基本使用:

          Lockr.set(‘name’, ‘Amy’);
          Lockr.set(‘age’, 28);
          Lockr.set(‘books’, [{title: ‘JavaScript’, price: 11.0}, {title: ‘Python’, price: 9.0}]);

          Barn

          https://github.com/arokor/barn

          Barn在localStorage之上提供了一個類似Redis的API。如果持久性很重要,那么你將需要這個庫來保持數據狀態,以防發生錯誤。

          基本使用:

          let barn = new Barn(localStorage); // 原始類型 barn.set(‘name’, ‘Amy’); let name = barn.get(‘name’);

          // Amy // List barn.lpush(‘names’, ‘Amy’);

          barn.lpush(‘names’, ‘James’); let name1 = barn.rpop(‘names’); // Amy let name2 = barn.rpop(‘names’);

          // James

          localForage

          https://github.com/localForag...

          這個簡單而快速的庫將通過IndexedDB或WebSQL使用異步存儲來改善Web的脫機體驗。它類似于localStorage,但具有回調功能。

          基本使用:

          localforage.setItem(‘name’, ‘Amy’, function(error, value) { // Do something });
          
          localforage.getItem(‘name’, function(error, value) { if (error) { console.log(‘an error occurs’);
            } else { // Do something with the value }
          });

          很神奇的是它提供中文文檔

          crypt.io

          https://github.com/jas-/crypt.io

          crypt.io使用標準JavaScript加密庫實現安全的瀏覽器存儲。使用crypto.io時,有三個存儲選項:sessionStorage,localStorage或cookie。

          基本使用:

          let storage = crypto; let book = { title: ‘JavaScript’, price: 13 };
          
          storage.set(‘book’, book, function(error, results) { if (error) { throw error;
            } // Do something });
          
          storage.get(‘book’, function(error, results) { if (error) { throw error; 
          

          } // Do something });

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          Promise 你真的用明白了么?

          seo達人

          前置知識

          在開始正文前,我們先把本文涉及到的一些內容提前定個基調。

          Promise 哪些 API 涉及了微任務?

          Promise 中只有涉及到狀態變更后才需要被執行的回調才算是微任務,比如說 then、 catch 、finally ,其他所有的代碼執行都是宏任務(同步執行)。

          上圖中藍色為同步執行,黃色為異步執行(丟到微任務隊列中)。

          這些微任務何時被加入微任務隊列?

          這個問題我們根據 ecma 規范來看:

          • 如果此時 Promise 狀態為 pending,那么成功或失敗的回調會分別被加入至 [[PromiseFulfillReactions]] 和 [[PromiseRejectReactions]] 中。如果你看過手寫 Promise 的代碼的話,應該能發現有兩個數組存儲這些回調函數。
          • 如果此時 Promise 狀態為非 pending 時,回調會成為 Promise Jobs,也就是微任務。

          了解完以上知識后,正片開始。

          同一個 then,不同的微任務執行

          初級

          Promise.resolve()
            .then(() => { console.log("then1"); Promise.resolve().then(() => { console.log("then1-1");
              });
            })
            .then(() => { console.log("then2");
            });

          以上代碼大家應該都能得出正確的答案:then1 → then1-1 → then2。

          雖然 then 是同步執行,并且狀態也已經變更。但這并不代表每次遇到 then 時我們都需要把它的回調丟入微任務隊列中,而是等待 then 的回調執行完畢后再根據情況執行對應操作。

          基于此,我們可以得出第一個結論:鏈式調用中,只有前一個 then 的回調執行完畢后,跟著的 then 中的回調才會被加入至微任務隊列。

          中級

          大家都知道了 Promise resolve 后,跟著的 then 中的回調會馬上進入微任務隊列。

          那么以下代碼你認為的輸出會是什么?

          let p = Promise.resolve();
          
          p.then(() => { console.log("then1"); Promise.resolve().then(() => { console.log("then1-1");
            });
          }).then(() => { console.log("then1-2");
          });
          
          p.then(() => { console.log("then2");
          }); 

          按照一開始的認知我們不難得出 then2 會在 then1-1 后輸出,但是實際情況卻是相反的。

          基于此我們得出第二個結論:每個鏈式調用的開端會首先依次進入微任務隊列。

          接下來我們換個寫法:

          let p = Promise.resolve().then(() => { console.log("then1"); Promise.resolve().then(() => { console.log("then1-1");
            });
          }).then(() => { console.log("then2");
          });
          
          p.then(() => { console.log("then3");
          });

          上述代碼其實有個陷阱,then 每次都會返回一個新的 Promise,此時的 p 已經不是 Promise.resolve() 生成的,而是最后一個 then 生成的,因此 then3 應該是在 then2 后打印出來的。

          順便我們也可以把之前得出的結論優化為:同一個 Promise 的每個鏈式調用的開端會首先依次進入微任務隊列。

          高級

          以下大家可以猜猜 then1-2 會在何時打印出來?

          Promise.resolve()
            .then(() => { console.log("then1"); Promise.resolve()
                .then(() => { console.log("then1-1"); return 1;
                })
                .then(() => { console.log("then1-2");
                });
            })
            .then(() => { console.log("then2");
            })
            .then(() => { console.log("then3");
            })
            .then(() => { console.log("then4");
            });

          這題肯定是簡單的,記住第一個結論就能得出答案,以下是解析:

          • 第一次 resolve 后第一個 then 的回調進入微任務隊列并執行,打印 then1
          • 第二次 resolve 后內部第一個 then 的回調進入微任務隊列,此時外部第一個 then 的回調全部執行完畢,需要將外部的第二個 then 回調也插入微任務隊列。
          • 執行微任務,打印 then1-1 和 then2,然后分別再將之后 then 中的回調插入微任務隊列
          • 執行微任務,打印 then1-2 和 then3 ,之后的內容就不一一說明了

          接下來我們把 return 1 修改一下,結果可就大不相同啦:

          Promise.resolve()
            .then(() => { console.log("then1"); Promise.resolve()
                .then(() => { console.log("then1-1"); return Promise.resolve();
                })
                .then(() => { console.log("then1-2");
                });
            })
            .then(() => { console.log("then2");
            })
            .then(() => { console.log("then3");
            })
            .then(() => { console.log("then4");
            });

          當我們 return Promise.resolve() 時,你猜猜 then1-2 會何時打印了?

          答案是最后一個才被打印出來。

          為什么在 then 中分別 return 不同的東西,微任務的執行順序竟有如此大的變化?以下是筆者的解析。

          PS:then 返回一個新的 Promise,并且會用這個 Promise 去 resolve 返回值,這個概念需要大家先了解一下。

          根據 Promise A+ 規范

          根據規范 2.3.2,如果 resolve 了一個 Promise,需要為其加上一個 then 并 resolve。

          if (x instanceof MyPromise) { if (x.currentState === PENDING) {
            } else {
              x.then(resolve, reject);
            } return;
          }

          上述代碼節選自手寫 Promise 實現。

          那么根據 A+ 規范來說,如果我們在 then 中返回了 Promise.resolve 的話會多入隊一次微任務,但是這個結論還是與實際不符的,因此我們還需要尋找其他權威的文檔。

          根據 ECMA - 262 規范

          根據規范 25.6.1.3.2,當 Promise resolve 了一個 Promise 時,會產生一個NewPromiseResolveThenableJob,這是屬于 Promise Jobs 中的一種,也就是微任務。

          This Job uses the supplied thenable and its then method to resolve the given promise. This process must take place as a Job to ensure that the evaluation of the then method occurs after evaluation of any surrounding code has completed.

          并且該 Jobs 還會調用一次 then 函數來 resolve Promise,這也就又生成了一次微任務。

          這就是為什么會觸發兩次微任務的來源。

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 平面設計服務

          基于Webpack4.X,小程序工程化落地實踐

          seo達人

          小程序開發現狀:

          1. 開發工具不好使用(無法熱更新,編譯緩慢);
          2. 無法使用css預處理語言(Sass、Less),有些IDE的插件可以監聽編譯,但不同編輯器需要額外安裝;
          3. 無法使用工程化(圖片自動壓縮,文件監聽編譯等);
          4. 編碼繁瑣(創建一個頁面,需要新建4個文件(.wxml、.js、.json、.wxss),每次新建都需要新建4次或者復制文件比較浪費時間);
          5. 團隊多人協作,代碼風格、使用的編輯器不一致;

          技術選型:

          在進行小程序項目啟動,進行技術選型的時候,對市場上多個小程序框架進行了考慮:

          • uni-app、mpVue、wepy、taro、 kbone

          團隊成員mpvue、wepy、uni-app都有實際的項目經驗,且根據Github上的star數還有issue,最后決定回到到使用原生開發。

          原因:

          雖然框架有些很成熟,有工程化和跨端的解決方案,也有實際的上線項目,但考慮到后續一些支撐性的問題(維護,文檔,坑等),在github上看了issue,有些已經沒在維護了。

          想著讓項目持續迭代,不受第三方框架限制,保持穩健,最后決定使用原生,跟著官方的迭代升級,自己維護,引入前端工程化的思想,提高繁瑣的流程以及開發效率。

          引入工程化

          1. 基于Webpack4.x,自定義Webpack配置

            • scss編譯為wxss:定義全局變量,使用公共的樣式文件,提高css開發效率和可維護性;

            • 自動壓縮圖片資源 : 小程序對包大小有限制,壓縮圖片大小可以減少空間,加快頁面加載;普通的圖片壓縮需要將圖片上傳到在線圖片壓縮網站,壓縮完再保存下來,效率比較低?,F在執行命令就可以自動壓縮圖片。

          2. 代碼規范

            • eslint: 能在js運行前就識別一些基礎的語法錯誤,減少不必要的小問題,提高調試效率;

            • husky、line-staged、prettier: 統一團隊代碼規范: 當執行代碼提交到git倉庫時,會將已改動文件的代碼格式化統一規范的代碼風格;

          1. 命令行創建頁面和組件模板

            • 小程序每次新建頁面或者組件,需要依賴4個文件(.wxml,.js,.wxss,.json)。只需要執行npm run create命令,會提示選擇創建頁面還是組件,選擇完成輸入頁面或者組件的名字,會自動生成4個模板文件(.wxml,.js,json,.scss)到對應的目錄

          1. 引入jest單元測試

            • 生成測試覆蓋率

          項目結構

          app -> 小程序程序的入口,使用微信開發者工具制定app目錄cli -> 生pagescomponents的模板腳手架img ->

           圖片資源原文件.eslintignore.eslintrc.js.gitignore(忽略wxss的提交,多人和做改動,容易有沖突,將scss文件傳到服務器就好了).prettierrc.js(代碼格式化風格配置)babel.config.jsjest.config.js(單元測試配置文件)webpack.compress.js(指定入口圖片資源文件,將圖片壓縮編譯到小程序的資源目錄)webpack.config.js -> (工程化入口文件,指定入口scss文件,監聽文件變化,自動將scss編譯為wxss)

          項目使用的包文件

          • webpack、babel、eslint: 轉換、規范js
          • chalk: console.log打印彩色顏色
          • scss、css-loader: 編譯scss
          • figlet: 控制臺顯示字體樣式
          • husky,line-staged,prettier: 代碼格式化相關
          • jest、miniprogram-simulate: 單元測試

          項目運行

          . 安裝依賴    npm install 或 yarn install. 編譯scss   

           npm run dev. 壓縮圖片    npm run img. 單元測試    npm run test(生成測試報告)    npm run test:watch(監聽測試文件改動—開發環境下使用)

          示例

          編譯scss

          執行 npm run dev

          壓縮圖片

          執行 npm run img

          將圖片壓縮到app/assets/img目錄下,一張7k的圖片變成5k,肉眼看不出有什么差別。

          新建頁面

          執行 npm run create

          終端會提示選擇頁面還是組件,選擇頁面,按Enter鍵,輸入頁面的名稱,會自動將4個文件創建到app/pages/xxx下。

          新建組件

          執行 npm run create

          終端會提示選擇頁面還是組件,選擇組件,按Enter鍵,輸入組件的名稱,會自動將4個文件創建到app/components/xxx下。

          單元測試

          執行 npm run test 生成測試報告執行 npm run test:watch 監聽測試文件,方便開發使用

          其他思考

          工程化的初衷就是為了減少重復性的操作,提高編碼的效率和樂趣。

          JavaScript是弱類型語言,好處是靈活,壞處是太靈活(多人協作,維護別人寫的代碼就是很痛苦了)。

          項目最主要的是穩健,可高度自定義拓展,不拘束于版本和地上那方,特別多人協作的團隊,工程化能給團隊帶來更多的收益,后續也會考慮將TypeScript等其他好的方案引入項目。

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          ES6 模塊知識點總結

          前端達人

          模塊化 export 和 import

          import 導入模塊、export 導出模塊
          可以直接在任何變量或者函數前面加上一個 export 關鍵字,就可以將它導出。
          在一個文件中:

          export const sqrt = Math.sqrt; export function square(x) { return x * x; } export function diag(x, y) { return sqrt(square(x) + square(y)); }  
            然后在另一個文件中這樣引用:
          import { square, diag } from 'lib'; console.log(square(11)); // 121 console.log(diag(4, 3));  

          總結

          //mod.js // 第一種模塊導出的書寫方式(一個個的導出) // 導出普通值 export let a = 12; export let b = 5; // 導出json export let json = { a, b }; // 導出函數 export let show = function(){ return 'welcome'; }; // 導出類 export class Person{ constructor(){ this.name = 'jam'; } showName(){ return this.name; } } //index.js //導出模塊如果用default了,引入的時候直接用,若沒有用default,引入的時候可以用{}的形式 // 導入模塊的方式 import { a, b, json, show, Person } from './mod.js'; console.log(a); // 12 console.log(b); // 5 console.log(json.a); // 12 console.log(json.b); // 5 console.log(show()); // welcome console.log(new Person().showName()); // jam //mod1.js // 第二種模塊導出的書寫方式 let a = 12; let b = 5; let c = 10; export { a, b, c as cc // as是別名,使用的時候只能用別名,特別注意下 }; //index1.js // 導入模塊的方式 import { a, b, cc // cc是導出的,as別名 } from './mod1.js'; console.log(a); // 12 console.log(b); // 5 console.log(cc); // 10 //mod2.js // 第三種模塊導出的書寫方式 ---> default // default方式的優點,import無需知道變量名,就可以直接使用,如下 // 每個模塊只允許一個默認出口 var name = 'jam'; var age = '28'; export default { name, age, default(){ console.log('welcome to es6 module of default...'); }, getName(){ return 'bb'; }, getAge(){ return 2; } }; //index2.js // 導入模塊的方式 import mainAttr from './mod2.js'; var str = ' '; // 直接調用 console.log(`我的英文名是:${mainAttr.name}我的年齡是${mainAttr.age}`); mainAttr.default(); // welcome to es6 module of default... console.log(mainAttr.getName()); // bb console.log(mainAttr.getAge()); // 2 //mod3.js var name = 'jam'; var age = '28'; export function getName(){ return name; }; export function getAge(){ return age; }; //index3.js // 導入模塊的方式 import * as fn from './mod3.js'; // 直接調用 console.log(fn.getName()); // 


          uni-app頁面之間的傳參,讓下一個頁面接收上一個頁面的值

          seo達人

          為了實現頁面之間的通訊,或者數據交換,我們要實現一個頁面到另一個頁面的傳參,可以通過點擊跳轉的時候進行頁面之間的傳值。


          <template>

             <view>

                 <navigator url="../a/a?id=1" hover-class="none">

                     <view>跳轉到A頁面</view>

                 </navigator>

                 <navigator url="../b/b?id=2" hover-class="none">

                     <view>跳轉到B頁面</view>

                 </navigator>

                 <navigator url="../c/c?id=3" hover-class="none">

                     <view>跳轉到C頁面</view>

                 </navigator>

             </view>

          </template>


          <script>

          export default {

             data() {

                 return {


                 }

             },

             methods: {


             },

             onLoad: function (option) {

                 //獲得上一個頁面傳過來的id

                 var pageid = option.id;

                 console.log(pageid);

             }

          }

          </script>

          Author:TANKING

          Web:http://www.likeyun.cn/

          Date:2020-8-13

          WeChat:face6009

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 平面設計服務

          如何實現小程序用戶增長,開發者給了幾點關鍵建議

          seo達人

          目前除了微信小程序,還有支付寶小程序,百度小程序,字節跳動小程序、京東小程序等,各大流量平臺都希望借助小程序的服務連接能力,為企業和用戶提供更好的服務。企業在小程序賽道上的戰略布局,可以借助平臺的流量,獲得更多的用戶和變現。

          有些人會認為小程序用戶增長是運營的事情,與產品和開發無關,實則不然。對于全棧工程師來說,他們不僅能做好小程序,還能玩溜小程序,真正全方位、無死角實現用戶增長最大化。

          小曉云近期采訪了幾位開發者,他們的小程序有些已經突破百萬用戶,在自己的領域中獲得持續性的發展。開發者圍繞用戶增長中的拉新、留存、轉化等維度,為我們提供了關于產品設計和開發的建議,希望以下的經驗對你們都有幫助。

          分享 1:小程序視覺和交互是重點,可以借鑒同行讓產品更符合大眾需求

          小程序的特點是「即搜即用,用完即走」,更適合那些輕量級的工具型應用。用戶來得快去得也快,則更需要簡潔、輕便的設計定位。UI 設計師和前端開發者平常可以通過知曉商店等小程序商店,多參考同類型優秀小程序的設計與交付,以此優化自己的小程序。雖然小程序視覺和交互并不會帶來用戶裂變式增長,但這個是留住用戶最基本的要求了。

          分享 2:微信小程序訂閱消息是一個用戶回流的神器

          今年微信小程序從模板消息升級為訂閱消息,這是一個幫助小程序實現用戶回流重要能力了。對于開發者來說,重點思考:如何合理的向用戶直觀傳達訂閱消息、在何處彈出訂閱消息。開發者在接入訂閱消息能力時,應該選擇適合業務場景的模板,并進行文案的引導,同時可以嘗試,發起一次訂閱時同時訂閱多個模版,讓用戶一次性獲得更多消息,提高訂閱率。新用戶粘性較低,可以借助訂閱消息發送獎勵通知等,召回用戶。

          分享 3:結合業務和用戶特點,策劃符合平臺調性的活動

          我們原先是做體育領域的資訊和商城 App,微信小程序發布后,龐大的微信流量給了我們一個拓展用戶的新機會,于是在 2017 年與知曉云結緣,并相伴 3 年了。用戶增長其實不是簡單的一兩句話可以總結,所以我先分享其中一點。體育領域的資訊,能吸引和留住用戶的,主要靠資訊的時效性、賽事活動、體育明星的加持,因此我們曾組織體育明星的投票活動,粉絲樂于參與,同時也提高了認同感和優越感。投票功能設置的門檻很低,但我們增加了很多排行榜的文案引導,加強粉絲的緊迫感,同時也增加了很多分享文案引導,讓粉絲主動分享。這個是針對體育領域上很成功的增長案例,當然也可以應用于其它追星平臺等。

          分享 4:做好數據分析,讓數據驅動用戶增長

          精細化運營的核心就是數據驅動,明確關鍵指標,并且通過數據分析的方式進行評估,然后不斷優化。數據埋點的缺點是開發成本高,所以我們是基于無埋點,一次性集成 SDK,采集頁面訪問、點擊行為、用戶特征等全量數據。

          分享 5:研究小程序平臺的用戶喜好,提供他們想要的服務

          我是做 QQ 小程序的, QQ 小程序的用戶對于社交、戀愛等偏娛樂的場景更感興趣,針對性提供頭像制作、起網名、小游戲等服務有比較大的市場。所以可以結合平臺用戶的特點和喜好,開發相關服務的小程序,更有利于小程序的發展和變現。

          以上內容均來自知曉云開發者的經驗分享,如果你對哪一個內容感興趣,可以在文末或者小曉云微信上留言,對于大家感興趣的內容,我們將再次邀請開發者進行更全面更完整的分享。

          知曉云成立三年以來,通過提供不斷更新的開發工具,幫助開發者提高開發效率,輕松完成優秀的作品。但我們服務不止于快、省,還要在增長與變現上賦能開發者/企業。

          知曉推送

          在線可視化操作加上業務系統輕松集成,可以一鍵推送全平臺的訂閱消息推送服務,輕松觸達億萬用戶。

          實時數據庫

          支持各類高實時性的業務場景,以簡便的開發方式、更高的時效性實現在云端和客戶端來的數據實時同步。通過實時數據庫實現的即時聊天室、投票、直播間送禮和彈幕、小游戲等互動功能,提升用戶留存和轉化。

          運營后臺

          一鍵生成可視化的運營管理后臺,User Dash API 和開箱即用的前端組件庫,開發者可以快速編寫一套獨立的運營后臺,并支持一鍵部署至知曉云服務器,是開發者的利器,也是運營者的福音。

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 平面設計服務

          模板商終結者——微信小商店

          seo達人

          微信小商店已經正式上線,對企業、個體和個人三種開店類型全量開放。微信小商店可以幫助商家免開發、零成本、一鍵生成賣貨小程序。微信小商店團隊將負責商品發布、訂單管理、交易結算、物流售后、直播帶貨等技術和服務流程。

          微信小商店個人開店非常簡單,3秒搞定,毫不夸張,堪稱模板商終結者。個人開店僅需身份認證即可,綁定銀行卡可以提現,1個微信號僅支持開通1個個人主體的小商店。

          企業、個體工商戶需要上傳營業執照、經營者信息、結算銀行賬戶信息等基礎信息,1個微信號可以開通3個“企業和個體”主體的小商店。

          當前微信小商店現階段支持售賣的商品類目超過1500個,主要包括:寵物生活、家用電器、手機、通訊、數碼、電腦、辦公、服飾內衣等,后續可售品類會增多。

          如何開店

          1. 只需搜索小程序 小商店助手
          2. 進入后只需填入店名等極少量信息,選擇個人店鋪的話不需要上傳資質
          3. 點擊確認就能極速擁有自己的小程序店鋪啦!
          4. 麻麻再也不用擔心我被模板商折磨啦!

          小程序助手

          在 小商店助手 里面還能查看店鋪數據在售商品、新增商品待付款商品、訂單管理、客服管理店鋪設置 等,功能非常強大!

          小程序助手

          小程序助手

          小程序助手

          小程序助手

          上架新商品也是非常簡單快捷,直接上傳商品圖片,加上標題和一些描述信息就可以。

          而且不論是開店審核還是商品上架審核,都非常迅速,作者嘗試了幾次都在一分鐘左右就審核完了!

          需要注意的一點是微信會收 0.6% 的交易金額提成哦,畢竟此路他開此樹他栽樹嘛~

          小程序助手

          小程序助手藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          微信小程序發送訂閱消息(之前是模板消息)

          seo達人

          之前的模板消息已經廢棄,現在改為訂閱消息,訂閱消息發布前,需要用戶確認后才能接收訂閱消息。


          image


          小程序端

          index.wxml


          <button bindtap="send">發送訂閱消息</button>

          index.js


          const app = getApp()

          Page({ data: {

           }, send:function(){

             wx.requestSubscribeMessage({ tmplIds: ['WZiCliW1zVtHXqX7dGnFNmFvxhW-wd9S_W4WfrwNvss'],


          success:(res)=> { // 在登錄的時候,獲取到的openid進行緩存,現在直接把openid提取出來即可 wx.getStorage({ key: 'openid',


                   success (res) { console.log(res.data)

                     wx.request({ url: 'https://www.xxx.com/send.php?openid='+res.data, data: {},


          header: { 'content-type': 'application/json' },


                       success (res) { // 推送 if(res.data.errcode == '43101'){ console.log("拒絕訂閱消息")

                         }else if(res.data.errcode == '0'){ console.log("發送訂閱消息")

                         }else{ console.log("未知錯誤")

                         }

                       }

                     })

                   },

                   fail (res) { console.log("沒有openid,無法發送")

                   }

                 })

               }

             })

           }

          })

          后端

          <?php //設置 header  header("Content-type:application/json"); //接收參數 $openid = $_GET["openid"];


          //初始化 CURL $ch = curl_init(); // 獲取access_token // include ''; require_once("access_token.php");


          //目標服務器地址  curl_setopt($ch, CURLOPT_URL,


          'https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token='.$access_token);


          //設置要POST的數據 curl_setopt($ch, CURLOPT_POST, true);


          $data = '{

           "touser": "'.$openid.'",

           "template_id": "模板ID",

           "page": "pages/index/index",// 要跳轉的頁面

           "lang":"zh_CN",

           "data": {

               "thing4": {

                   "value": "歡迎使用專插本最前線小程序"

               },

               "thing5": {

                   "value": "小程序由公眾號:廣東專插本最前線開發"

               }

           }

          }';

          curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

          curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); // 對認證證書來源的檢查 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); // 從證書中檢查SSL加密算法是否存在 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); //獲取的信息以文件流的形式返回,而不是直接輸出 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //發起請求 $result = curl_exec($ch); echo $result; //關閉請求 curl_close($ch); ?>

          access_token.php


          <?php // 聲明頁面header header("Content-type:charset=utf-8"); // APPID、APPSECRET $appid = "你的小程序APPID";

          $appsecret = "你的小程序APPSECRET"; // 獲取access_token和jsapi_ticket function getToken(){

             $file = file_get_contents("access_token.json",true);//讀取access_token.json里面的數據 $result = json_decode($file,true); //判斷access_token是否在有效期內,如果在有效期則獲取緩存的access_token //如果過期了則請求接口生成新的access_token并且緩存access_token.json if (time() > $result['expires']){

                 $data = array();

                 $data['access_token'] = getNewToken();

                 $data['expires'] = time()+7000;

                 $jsonStr =  json_encode($data);

                 $fp = fopen("access_token.json", "w");

                 fwrite($fp, $jsonStr);

                 fclose($fp); return $data['access_token'];

             }else{ return $result['access_token'];

             }

          } //獲取新的access_token function getNewToken($appid,$appsecret){ global $appid; global $appsecret;

             $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=".$appid."&secret=".$appsecret."";

             $access_token_Arr =  file_get_contents($url);

             $token_jsonarr = json_decode($access_token_Arr, true); return $token_jsonarr["access_token"];

          }


          $access_token = getToken(); ?>

          邏輯

          1、通過button控件出發send函數

          2、send函數調用wx.requestSubscribeMessageAPI,微信允許接收訂閱消息

          3、 wx.request向send.php后端請求

          4、后端獲取access_token后,調用訂閱消息接口POST一段json數據即可發送訂閱消息


          官方文檔

          1、https://developers.weixin.qq.com/miniprogram/dev/api/open-api/subscribe-message/wx.requestSubscribeMessage.html


          2、https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.addTemplate.html


          Author:TANKING

          Date:2020-08-24

          Web:http://www.likeyun.cn/

          WeChat:face6009

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 平面設計服務

          Flutter 實戰:增刪查改功能示例代碼

          seo達人

          七月,我們上線重磅基礎能力——實時數據庫,并開了實戰直播課,讓大家可以更好的理解并使用該服務。你的聊天室、站內信、投票、小游戲等需要高實時的功能正在想你招手,趕緊使用實時數據庫服務又快又簡單的開發它們吧。


          點擊此處回顧教學視頻,看看知曉云大前端組長如何在十分鐘內搞定一個視頻彈幕微信小程序。


          Ps:目前實時數據庫限時免費,就算以后收費,費用也是低到忽略不計。速速用上,不要錯過這么硬核的能力。


          八月,我們迎來知曉云三周年,推出各式各樣的福利活動。開發者在這個全年最優惠的時間里,升級、續費,甚至購買三年期包年套餐,與知曉云鎖定下一個三年。非常感謝大家的支持,我們會繼續努力,不斷輸出更強大的能力。


          九月初,Flutter SDK 已進入測試階段,很快就可以跟大家見面了。

          Flutter SDK 的使用比較簡明易懂,例如對數據表的增刪查改,在指定數據表后,對數據項進行對應操作即可,例如新增(create)、查找(get)、修改(update)和刪除(delete)。


          以下是對 Flutter 增刪查改功能進行展示:


          TableObject product = new TableObject('product'); // 獲取名為 product 的數據表


          // 新增數據

          TableRecord record = product.create(); // 創建一條空白記錄


          // 為屬性字段賦值

          record.set('name', '知曉云 flutter sdk'); // 對 name 字段進行賦值

          record.set('version', '1.0'); // 對 version 字段進行賦值


          // 將數據保存到服務器

          record = await record.save(); // 保存


          // 從服務器獲取一條數據

          TableRecord record = await product.get(record.id);


          // 更新數據

          record.set('version', '1.1');

          await record.update();


          // 刪除數據

          await product.delete(recordId: record.id);

          目前知曉云 Flutter SDK 支持的功能如下:


          數據表

          用戶

          內容庫

          文件

          云函數調用

          獲取服務器時間

          本地存儲

          Flutter SDK 正式上線后,我們還會輸出實戰教學視頻,敬請期待!


          另外,我們提前開啟內測申請通道,點擊此處或微信掃一掃掃描下方卡片二維碼即可申請,獲得內測資格的開發者,不僅可優先體驗新功能,同時還可以與知曉云工程師近距離交流,你使用后的建議也可以得到更快的反饋與實現。


          知曉云 Flutter SDK


          2020 年已過去三分之二,好消息是,即將到來的中秋&國慶小長假以及知曉云近期的更新內容,除了即將上線的 Flutter SDK ,還有以下更新。


          1. 支持 QQ 小程序訂閱消息,消息能力又前進一步。

          與微信訂閱消息不同在于,QQ 小程序訂閱消息不僅支持分為「一次性訂閱」,還支持「長期訂閱」,如果用戶之前已經同意授權長期訂閱,則不會再出現彈窗詢問。>>> 查看開發文檔


          2. iOS 和 Android SDK 支持微博登錄。


          查看 iOS 開發文檔

          查看 Android 開發文檔

          如果你有其他需求,可以通過文末

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          日歷

          鏈接

          個人資料

          藍藍設計的小編 http://www.syprn.cn

          存檔

          亚洲va欧美va天堂v国产综合