首先看看我們的素材:
當拿到一張原始素材的時候!
怎么辦?該怎么入手?怎么找方向?
來吧!
先來看看成稿:
最終設(shè)計成果還可以,那么是通過怎樣的設(shè)計手法達到這樣的效果呢?
下面我就來分享一下我的作圖思路;
1.需求的梳理和信息收集:
理解核心需求,為設(shè)計方向做好前期準備
Slogan:傳武(作品名) 副文案:幽府之力,逆轉(zhuǎn)生死
我們漫畫類的作品眾多,內(nèi)容風格繁雜,所以拿到需求之后,首先就是要對作品進行“調(diào)查”。通過對漫畫作品的閱讀,理解內(nèi)容、故事、繪畫風格來定性設(shè)計的方向。這樣才能在設(shè)計過程中準確把握住作品調(diào)性,才能設(shè)計出最貼合作品風格內(nèi)容的banner,才能把我們作品精髓的內(nèi)容傳遞給用戶。
比如上面這部作品,SLOGAN“傳武”是我們要著重設(shè)計展示的。而副標題“幽府之力,逆轉(zhuǎn)生死”也很重要,往往傳達出了作品的賣點和調(diào)性。
再看看我們拿到的素材,一張單人的簡單素材,看起來很單調(diào),似乎沒有可切入的地方。這個時候就體現(xiàn)出為什么要先對作品進行“調(diào)查”的重要性了!
2. 確定設(shè)計方向:
明確設(shè)計方向,精準展現(xiàn)作品調(diào)性
通過閱讀,我們了解到這部作品是一部熱血,古風,玄幻作品,還有大致的故事內(nèi)容,再結(jié)合我們的副標題“幽府之力,逆轉(zhuǎn)生死”。
腦子里就已經(jīng)開始有畫面了,“幽府”大概表達了一個場景,而“力量”和“逆轉(zhuǎn)生死”又傳達出了一種氣勢磅礴的場面。這就為我們設(shè)計的方向奠定了一個準確的方向。
首先就把我們的素材和文案拉進畫框里,進行一個大致的排版找找感覺。第一個左右排版就太常規(guī)了,在場面和氣勢上有些弱。第二個添加了漫畫框,想切入一些故事內(nèi)容一起展示。但又有一些強調(diào)漫畫框的存在了,磅礴的場景沒有展示出來。但注意右邊的部分,把主標題排在人物的兩邊似乎是一個不錯的選擇!我們就從這里入手。
重新嘗試了一下,發(fā)現(xiàn)這樣布局好像就是我們想要的感覺哈!那既然確定了框架,我們接下來就按照這個方向繼續(xù)強化出“氣勢”“力量”的感覺。
我們以人物為中心,想象畫面里有力量從人物背后向外“迸發(fā)”所以我們的字體可以設(shè)計得有一些趨勢在里邊,也是以一個圓弧為中心向外生長。以光從人物背面照射出來大逆光的視覺,營造一種“力量”迸發(fā)的感覺。
3. 顏色的選擇:
跟隨之前確定的設(shè)計方向,提煉選色搭配。
我的方法一般是先從素材本身出發(fā),根據(jù)想要達成的視覺風格來延伸出想要的配色。這樣得到的顏色更整體,人物素材能更好的融入背景氛圍中,也方便后期調(diào)整。觀察的素材,發(fā)現(xiàn)他的顏色都比較灰,缺乏對比,就會顯得很“平”,難以營造出我們想要的感覺。所以從人物素材本身的顏色出發(fā),提取同類色和提高飽和度。結(jié)合考慮到有利于運營推廣的視覺需要“吸睛”。得出了后面一組對比更強烈的顏色。
4. 有主次地進行深入刻畫:
畫面中最主要的肯定是我們的SLOGAN和人物角色,是我們要重點刻畫的對象。剩下的副標題、背景氛圍次之。不僅是要在排版上做區(qū)分,在視覺感受上也要做出差異化。這樣才能有遠近虛實的感覺,增加空間感。
我們希望畫面具有一定的質(zhì)感,增加其沖擊力。所以我們在刻畫背景的時候可以選擇一些漫畫里比較好的場景,或扉頁背景素材來做底圖。再疊加上一些紋理材質(zhì),再一層一層地來給背景打光,用“疊加”“柔光”“濾色”等圖層屬性來慢慢提高亮度,最終達到我們想要的效果。
小技巧1:相同光源的照射,傳達到不同的物體上時,它的視覺表現(xiàn)時不同的。并不是光源時什么顏色,照射的地方就會是什么顏色。
我們來對比一下兩種顏色的實際效果,可以說是很直觀了!
小技巧2:為了使素材更完美地融入到背景中,我們可以后期人為地給素材增加一圈高光/輪廓光。這樣使畫面更融洽的同時,也能讓我們的人物變得立體起來!。
再來對比下沒加輪廓光的感覺:
真的是少了些味道和細節(jié)哈哈,其實在很多時候我們都可以對我們的素材進行二次加工讓其提升一定的品質(zhì),配合畫面以達到更好的視覺效果。
5. SLOGAN的設(shè)計:
主文案在我們草圖的基礎(chǔ)上,結(jié)合整體畫面的趨勢進行細化。(增加毛筆筆觸,和優(yōu)化筆畫)。
這里主要分了三層進行處理顏色層(文字層):主要給一個基礎(chǔ)顏色;
材質(zhì)層:因為這兩個字的占比比較大,所以可以增加一些紋理細節(jié)讓畫面更豐富耐看;
厚度層:讓后面的光源,在我們的字上形成一圈高光,可以突出我們的文字。
增加一些光暈效果,再放上做好的SLOGAN看看效果,好像還不錯。
有些同學(xué)可能會疑惑這里為什么字體要做一個厚度層,我們也上一下對比圖先看下效果:
可以看少了一些些質(zhì)感和重量,在輕量的風格里ok,但是在我們當前的畫面里就差了些感覺,所以才做了厚度層來強調(diào)光線照射過來的視覺增強畫面沖擊力。
之后主要是做一些符合我們畫面氛圍的漂浮元素,豐富畫面。有一定手繪功底的話就再好不過了!
完成,到這一步差不多達成了我們想要實現(xiàn)的效果,“氣勢”和“力量”的感覺在這么“樸實無華”的素材身上也基本表現(xiàn)到位了。還是比較滿意的,差不多可以提審交稿了!
最后在觀察觀察整體的畫面,審視一遍,查缺補漏。
發(fā)現(xiàn)我們的畫面好像有些燥啊,紅色和黃色飽和略微有些高。整體的感覺也不夠清晰。最后再做一個調(diào)整吧。
降低一些紅色和黃色飽和度,在暗部加一些紫色(主文案暗的部分和畫面四周的暗部)。增加冷暖對比就好多了。
小技巧3:蓋印整個圖層——在濾鏡里面找到其他——高反差保留,數(shù)值根據(jù)畫面來調(diào)。
然后就得到這么一個圖層
是不是很神奇?別慌,把這個圖層的屬性改成線性光看看,畫面清晰了很多,也變得更有質(zhì)感了!
最后看下過程演變圖:
總結(jié)
1)梳理需求內(nèi)容:通過閱讀漫畫作品,深入了解內(nèi)容并收集整理信息;
2)提煉關(guān)鍵字延展:嘗試多種可行方案,最終確定設(shè)計方向;
3)slogan的設(shè)計:一定要符合畫面和作品調(diào)性,達到與畫面相映成輝的效果;
4)細節(jié)把控:完成之后再回過頭來審視整體畫面,查漏補缺力求做到最好!
文章來源:UI中國 作者:騰訊動漫TCD
藍藍設(shè)計( www.yvirxh.cn )是一家專注而深入的界面設(shè)計公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計、BS界面設(shè)計 、 cs界面設(shè)計 、 ipad界面設(shè)計 、 包裝設(shè)計 、 圖標定制 、 用戶體驗 、交互設(shè)計、 網(wǎng)站建設(shè) 、平面設(shè)計服務(wù)
構(gòu)建庫的常見方法有兩種:一種是自己手動構(gòu)建webpack庫打包,設(shè)置output為 library; 另一種是基于vue-cli3輸出庫資源包。我們采用第二種vue腳手架的方式構(gòu)建庫。
新增編譯庫命令
// package.json
"scripts": {
// ...
"lib": "vue-cli-service build --target lib --name Step --dest dist packages/index.js"
}
// packages/index.js 默認打包Step
import Step from '../steps/src/step';
Step.install = function(Vue) {
Vue.component(Step.name, Step);
};
export default Step;
--name: 庫名稱。
--target: 構(gòu)建目標,默認為應(yīng)用模式。這里修改為 lib 啟用庫模式。
--dest: 輸出目錄,默認 dist。
[entry]: 最后一個參數(shù)為入口文件,默認為 src/App.vue。
更多詳細配置查看 ? vue腳手架官網(wǎng)
如果該庫依賴于其他庫,請在vue.config.js 配置 externals
// vue.config.js
module.exports = {
configureWebpack:{
externals: {
vue: 'Vue',
'vue-router':'VueRouter',
axios: 'axios'
}
}
}
執(zhí)行 npm run lib 就可以發(fā)現(xiàn)我們的庫被打包到了 根目錄的dist文件夾下。
添加 .npmignore 文件(可選)
和 .gitignore 的語法一樣,具體需要提交什么文件,看各自的實際情況
# 忽略目錄
examples/
packages/
public/
# 忽略指定文件
vue.config.js
babel.config.js
*.map
配置npm庫信息
配置package.json文件,以發(fā)布庫文件。
{
"name": "gis",
"version": "1.2.5",
"description": "基于 Vue 的庫文件",
"main": "dist/gis.umd.min.js",
"keyword": "vue gis",
"private": false,
"files": ["dist"],
"license": "MIT"
}
name: 包名,該名字是唯一的??稍?npm 官網(wǎng)搜索名字,如果存在則需換個名字。
version: 版本號,每次發(fā)布至 npm 需要修改版本號,不能和歷史版本號相同。
description: 描述。
main: 入口文件,該字段需指向我們最終編譯后的包文件。
keyword:關(guān)鍵字,以空格分離希望用戶最終搜索的詞。
author:作者
files: 要上傳的文件
private:是否私有,需要修改為 false 才能發(fā)布到 npm
license: 開源協(xié)議
dependencies: 依賴庫
注意每次發(fā)布新的庫,需要更改版本號,規(guī)則如下:
"version": "1.2.5" 主版本號為 1,次版本號 2,修訂號 5
主版本號(Major):當你做了不兼容的API修改
次版本號(Minor):當你做了向下兼容的功能性新增
修訂號(Patch):當你做了向下兼容的問題修正
登錄npm
首先設(shè)置登錄的npm鏡像地址
npm config set registry http://168.20.20.57.4873
然后在終端執(zhí)行登錄命令,輸入用戶名、密碼、郵箱即可登錄
npm login
接著發(fā)布庫資源到npm
npm publish
最后發(fā)布成功可到官網(wǎng)查看對應(yīng)的包并下載
npm install package_name
藍藍設(shè)計( www.yvirxh.cn )是一家專注而深入的界面設(shè)計公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計、BS界面設(shè)計 、 cs界面設(shè)計 、 ipad界面設(shè)計 、 包裝設(shè)計 、 圖標定制 、 用戶體驗 、交互設(shè)計、 網(wǎng)站建設(shè) 、平面設(shè)計服務(wù)目錄
1.3 函數(shù)聲明與函數(shù)表達式的區(qū)別
1.4 構(gòu)造函數(shù)Function(了解即可,一般不用)
4.1.1 新的函數(shù)調(diào)用方式apply和call方法
定義函數(shù)的方式有三種:
new Function
(一般不用)
-
// 函數(shù)的聲明
-
function fn() {
-
console.log("我是JS中的一等公民-函數(shù)!!!哈哈");
-
}
-
fn();
函數(shù)表達式就是將一個匿名函數(shù)賦值給一個變量。函數(shù)表達式必須先聲明,再調(diào)用。
-
// 函數(shù)表達式
-
var fn = function() {
-
console.log("我是JS中的一等公民-函數(shù)!!!哈哈");
-
};
-
fn();
下面是一個根據(jù)條件定義函數(shù)的例子:
-
if (true) {
-
function f () {
-
console.log(1)
-
}
-
} else {
-
function f () {
-
console.log(2)
-
}
-
}
以上代碼執(zhí)行結(jié)果在不同瀏覽器中結(jié)果不一致。我們可以使用函數(shù)表達式解決上面的問題:
-
var f
-
-
if (true) {
-
f = function () {
-
console.log(1)
-
}
-
} else {
-
f = function () {
-
console.log(2)
-
}
-
}
函數(shù)聲明如果放在if-else的語句中,在IE8的瀏覽器中會出現(xiàn)問題,所以為了更好的兼容性我們以后最好用函數(shù)表達式,不用函數(shù)聲明的方式。
在前面的學(xué)習(xí)中我們了解到函數(shù)也是對象。注意:函數(shù)是對象,對象不一定是函數(shù),對象中有__proto__原型,函數(shù)中有prototype原型,如果一個東西里面有prototype,又有__proto__,說明它是函數(shù),也是對象。
-
function F1() {}
-
-
console.dir(F1); // F1里面有prototype,又有__proto__,說明是函數(shù),也是對象
-
-
console.dir(Math); // Math中有__proto__,但是沒有prorotype,說明Math不是函數(shù)
對象都是由構(gòu)造函數(shù)創(chuàng)建出來的,函數(shù)既然是對象,創(chuàng)建它的構(gòu)造函數(shù)又是什么呢?事實上所有的函數(shù)實際上都是由Function構(gòu)造函數(shù)創(chuàng)建出來的實例對象。
所以我們可以使用Function構(gòu)造函數(shù)創(chuàng)建函數(shù)。
語法:new Function(arg1,arg2,arg3..,body);
arg是任意參數(shù),字符串類型的。body是函數(shù)體。
-
// 所有的函數(shù)實際上都是Function的構(gòu)造函數(shù)創(chuàng)建出來的實例對象
-
var f1 = new Function("num1", "num2", "return num1+num2");
-
console.log(f1(10, 20));
-
console.log(f1.__proto__ == Function.prototype);
-
-
// 所以,函數(shù)實際上也是對象
-
console.dir(f1);
-
console.dir(Function);
-
// 普通函數(shù)
-
function f1() {
-
console.log("我是普通函數(shù)");
-
}
-
f1();
-
-
// 構(gòu)造函數(shù)---通過new 來調(diào)用,創(chuàng)建對象
-
function F1() {
-
console.log("我是構(gòu)造函數(shù)");
-
}
-
var f = new F1();
-
-
// 對象的方法
-
function Person() {
-
this.play = function() {
-
console.log("我是對象中的方法");
-
};
-
}
-
var per = new Person();
-
per.play();
this
的指向
函數(shù)的調(diào)用方式?jīng)Q定了 this
指向的不同:
調(diào)用方式 | 非嚴格模式 | 備注 |
---|---|---|
普通函數(shù)調(diào)用 | window | 嚴格模式下是 undefined |
構(gòu)造函數(shù)調(diào)用 | 實例對象 | 原型方法中 this 也是實例對象 |
對象方法調(diào)用 | 該方法所屬對象 | 緊挨著的對象 |
事件綁定方法 | 綁定事件對象 | |
定時器函數(shù) | window |
-
// 普通函數(shù)
-
function f1() {
-
console.log(this); // window
-
}
-
f1();
-
-
// 構(gòu)造函數(shù)
-
function Person() {
-
console.log(this); // Person
-
// 對象的方法
-
this.sayHi = function() {
-
console.log(this); // Person
-
};
-
}
-
// 原型中的方法
-
Person.prototype.eat = function() {
-
console.log(this); // Person
-
};
-
var per = new Person();
-
console.log(per); // Person
-
per.sayHi();
-
per.eat();
-
-
// 定時器中的this
-
setInterval(function() {
-
console.log(this); // window
-
}, 1000);
了解了函數(shù) this 的指向之后,我們知道在一些情況下我們?yōu)榱耸褂媚撤N特定環(huán)境的 this 引用,需要采用一些特殊手段來處理,例如我們經(jīng)常在定時器外部備份 this 引用,然后在定時器函數(shù)內(nèi)部使用外部 this 的引用。
然而實際上 JavaScript 內(nèi)部已經(jīng)專門為我們提供了一些函數(shù)方法,用來幫我們更優(yōu)雅的處理函數(shù)內(nèi)部 this 指向問題。這就是接下來我們要學(xué)習(xí)的 call、apply、bind 三個函數(shù)方法。call()、apply()、bind()這三個方法都是是用來改變this的指向的。
call()
方法調(diào)用一個函數(shù), 其具有一個指定的 this
值和分別地提供的參數(shù)(參數(shù)的列表)。
apply()
方法調(diào)用一個函數(shù), 其具有一個指定的 this
值,以及作為一個數(shù)組(或類似數(shù)組的對象)提供的參數(shù)。
注意:call()
和 apply()
方法類似,只有一個區(qū)別,就是 call()
方法接受的是若干個參數(shù)的列表,而 apply()
方法接受的是一個包含多個參數(shù)的數(shù)組。
call語法:
fun.call(thisArg[, arg1[, arg2[, ...]]])
call參數(shù):
thisArg
arg1, arg2, ...
apply語法:
fun.apply(thisArg, [argsArray])
apply參數(shù):
thisArg
argsArray
apply()
與 call()
相似,不同之處在于提供參數(shù)的方式。
apply()
使用參數(shù)數(shù)組而不是一組參數(shù)列表。例如:
fun.apply(this, ['eat', 'bananas'])
-
function f1(x, y) {
-
console.log("結(jié)果是:" + (x + y) + this);
-
return "666";
-
}
-
f1(10, 20); // 函數(shù)的調(diào)用
-
-
console.log("========");
-
-
// apply和call方法也是函數(shù)的調(diào)用的方式
-
// 此時的f1實際上是當成對象來使用的,對象可以調(diào)用方法
-
// apply和call方法中如果沒有傳入?yún)?shù),或者是傳入的是null,那么調(diào)用該方法的函數(shù)對象中的this就是默認的window
-
f1.apply(null, [10, 20]);
-
f1.call(null, 10, 20);
-
-
// apply和call都可以讓函數(shù)或者方法來調(diào)用,傳入?yún)?shù)和函數(shù)自己調(diào)用的寫法不一樣,但是效果是一樣的
-
var result1 = f1.apply(null, [10, 20]);
-
var result2 = f1.call(null, 10, 20);
-
console.log(result1);
-
console.log(result2);
-
// 通過apply和call改變this的指向
-
function Person(name, sex) {
-
this.name = name;
-
this.sex = sex;
-
}
-
//通過原型添加方法
-
Person.prototype.sayHi = function(x, y) {
-
console.log("您好啊:" + this.name);
-
return x + y;
-
};
-
var per = new Person("小三", "男");
-
var r1 = per.sayHi(10, 20);
-
-
console.log("==============");
-
-
function Student(name, age) {
-
this.name = name;
-
this.age = age;
-
}
-
var stu = new Student("小舞", 18);
-
var r2 = per.sayHi.apply(stu, [10, 20]);
-
var r3 = per.sayHi.call(stu, 10, 20);
-
-
console.log(r1);
-
console.log(r2);
-
console.log(r3);
apply和call都可以改變this的指向。調(diào)用函數(shù)的時候,改變this的指向:
-
// 函數(shù)的調(diào)用,改變this的指向
-
function f1(x, y) {
-
console.log((x + y) + ":===>" + this);
-
return "函數(shù)的返回值";
-
}
-
//apply和call調(diào)用
-
var r1 = f1.apply(null, [1, 2]); // 此時f1中的this是window
-
console.log(r1);
-
var r2 = f1.call(null, 1, 2); // 此時f1中的this是window
-
console.log(r2);
-
console.log("=============>");
-
//改變this的指向
-
var obj = {
-
sex: "男"
-
};
-
// 本來f1函數(shù)是window對象的,但是傳入obj之后,f1的this此時就是obj對象
-
var r3 = f1.apply(obj, [1, 2]); //此時f1中的this是obj
-
console.log(r3);
-
var r4 = f1.call(obj, 1, 2); //此時f1中的this是obj
-
console.log(r4);
調(diào)用方法的時候,改變this的指向:
-
//方法改變this的指向
-
function Person(age) {
-
this.age = age;
-
}
-
Person.prototype.sayHi = function(x, y) {
-
console.log((x + y) + ":====>" + this.age); //當前實例對象
-
};
-
-
function Student(age) {
-
this.age = age;
-
}
-
var per = new Person(10); // Person實例對象
-
var stu = new Student(100); // Student實例對象
-
// sayHi方法是per實例對象的
-
per.sayHi(10, 20);
-
per.sayHi.apply(stu, [10, 20]);
-
per.sayHi.call(stu, 10, 20);
總結(jié)
apply的使用語法:
1 函數(shù)名字.apply(對象,[參數(shù)1,參數(shù)2,...]);
2 方法名字.apply(對象,[參數(shù)1,參數(shù)2,...]);
call的使用語法
1 函數(shù)名字.call(對象,參數(shù)1,參數(shù)2,...);
2 方法名字.call(對象,參數(shù)1,參數(shù)2,...);
它們的作用都是改變this的指向,不同的地方是參數(shù)傳遞的方式不一樣。
如果想使用別的對象的方法,并且希望這個方法是當前對象的,就可以使用apply或者是call方法改變this的指向。
bind() 函數(shù)會創(chuàng)建一個新函數(shù)(稱為綁定函數(shù)),新函數(shù)與被調(diào)函數(shù)(綁定函數(shù)的目標函數(shù))具有相同的函數(shù)體(在 ECMAScript 5 規(guī)范中內(nèi)置的call屬性)。當目標函數(shù)被調(diào)用時 this 值綁定到 bind() 的第一個參數(shù),該參數(shù)不能被重寫。綁定函數(shù)被調(diào)用時,bind() 也可以接受預(yù)設(shè)的參數(shù)提供給原函數(shù)。一個綁定函數(shù)也能使用new操作符創(chuàng)建對象:這種行為就像把原函數(shù)當成構(gòu)造器。提供的 this 值被忽略,同時調(diào)用時的參數(shù)被提供給模擬函數(shù)。
bind方法是復(fù)制的意思,本質(zhì)是復(fù)制一個新函數(shù),參數(shù)可以在復(fù)制的時候傳進去,也可以在復(fù)制之后調(diào)用的時候傳入進去。apply和call是調(diào)用的時候改變this指向,bind方法,是復(fù)制一份的時候,改變了this的指向。
語法:
fun.bind(thisArg[, arg1[, arg2[, ...]]])
參數(shù):
thisArg
arg1, arg2, ...
返回值:
返回由指定的this值和初始化參數(shù)改造的原函數(shù)的拷貝。
示例1:
-
function Person(name) {
-
this.name = name;
-
}
-
Person.prototype.play = function() {
-
console.log(this + "====>" + this.name);
-
};
-
-
function Student(name) {
-
this.name = name;
-
}
-
var per = new Person("人");
-
var stu = new Student("學(xué)生");
-
per.play();
-
// 復(fù)制了一個新的play方法
-
var ff = per.play.bind(stu);
-
ff();
示例2:
-
//通過對象,調(diào)用方法,產(chǎn)生隨機數(shù)
-
function ShowRandom() {
-
//1-10的隨機數(shù)
-
this.number = parseInt(Math.random() * 10 + 1);
-
}
-
//添加原型方法
-
ShowRandom.prototype.show = function() {
-
//改變了定時器中的this的指向了
-
window.setTimeout(function() {
-
//本來應(yīng)該是window, 現(xiàn)在是實例對象了
-
//顯示隨機數(shù)
-
console.log(this.number);
-
}.bind(this), 1000);
-
};
-
//實例對象
-
var sr = new ShowRandom();
-
//調(diào)用方法,輸出隨機數(shù)字
-
sr.show();
call 和 apply 特性一樣
this
的指向
null
或者 undefined
則內(nèi)部 this 指向 window
bind
-
function fn(x, y, z) {
-
console.log(fn.length) // => 形參的個數(shù)
-
console.log(arguments) // 偽數(shù)組實參參數(shù)集合
-
console.log(arguments.callee === fn) // 函數(shù)本身
-
console.log(fn.caller) // 函數(shù)的調(diào)用者
-
console.log(fn.name) // => 函數(shù)的名字
-
}
-
-
function f() {
-
fn(10, 20, 30)
-
}
-
-
f()
函數(shù)可以作為參數(shù),也可以作為返回值。
函數(shù)是可以作為參數(shù)使用,函數(shù)作為參數(shù)的時候,如果是命名函數(shù),那么只傳入命名函數(shù)的名字,沒有括號。
-
function f1(fn) {
-
console.log("我是函數(shù)f1");
-
fn(); // fn是一個函數(shù)
-
}
-
-
//傳入匿名函數(shù)
-
f1(function() {
-
console.log("我是匿名函數(shù)");
-
});
-
// 傳入命名函數(shù)
-
function f2() {
-
console.log("我是函數(shù)f2");
-
}
-
f1(f2);
作為參數(shù)排序案例:
-
var arr = [1, 100, 20, 200, 40, 50, 120, 10];
-
//排序---函數(shù)作為參數(shù)使用,匿名函數(shù)作為sort方法的參數(shù)使用,此時的匿名函數(shù)中有兩個參數(shù),
-
arr.sort(function(obj1, obj2) {
-
if (obj1 > obj2) {
-
return -1;
-
} else if (obj1 == obj2) {
-
return 0;
-
} else {
-
return 1;
-
}
-
});
-
console.log(arr);
-
function f1() {
-
console.log("函數(shù)f1");
-
return function() {
-
console.log("我是函數(shù),此時作為返回值使用");
-
}
-
-
}
-
-
var ff = f1();
-
ff();
作為返回值排序案例:
-
// 排序,每個文件都有名字,大小,時間,可以按照某個屬性的值進行排序
-
// 三個文件,文件有名字,大小,創(chuàng)建時間
-
function File(name, size, time) {
-
this.name = name; // 名字
-
this.size = size; // 大小
-
this.time = time; // 創(chuàng)建時間
-
}
-
var f1 = new File("jack.avi", "400M", "1999-12-12");
-
var f2 = new File("rose.avi", "600M", "2020-12-12");
-
var f3 = new File("albert.avi", "800M", "2010-12-12");
-
var arr = [f1, f2, f3];
-
-
function fn(attr) {
-
// 函數(shù)作為返回值
-
return function getSort(obj1, obj2) {
-
if (obj1[attr] > obj2[attr]) {
-
return 1;
-
} else if (obj1[attr] == obj2[attr]) {
-
return 0;
-
} else {
-
return -1;
-
}
-
}
-
}
-
console.log("按照名字排序:**********");
-
// 按照名字排序
-
var ff = fn("name");
-
// 函數(shù)作為參數(shù)
-
arr.sort(ff);
-
for (var i = 0; i < arr.length; i++) {
-
console.log(arr[i].name + "====>" + arr[i].size + "===>" + arr[i].time);
-
}
-
-
console.log("按照大小排序:**********");
-
// 按照大小排序
-
var ff = fn("size");
-
// 函數(shù)作為參數(shù)
-
arr.sort(ff);
-
for (var i = 0; i < arr.length; i++) {
-
console.log(arr[i].name + "====>" + arr[i].size + "===>" + arr[i].time);
-
}
-
-
console.log("按照創(chuàng)建時間排序:**********");
-
// 按照創(chuàng)建時間排序
-
var ff = fn("time");
-
// 函數(shù)作為參數(shù)
-
arr.sort(ff);
-
for (var i = 0; i < arr.length; i++) {
-
console.log(arr[i].name + "====>" + arr[i].size + "===>" + arr[i].time);
-
}
-
無縫輪播一直是面試的熱門題目,而大部分答案都是復(fù)制第一張到最后。誠然,這種方法是非常標準,那么有沒有另類一點的方法呢?
第一種方法是需要把所有圖片一張張擺好,然后慢慢移動的,
但是我能不能直接不擺就硬移動呢?
如果你使用過vue的transition
,我們是可以通過給每一張圖片來添加入場動畫和離場動畫來模擬這個移動
這樣看起來的效果就是圖片從右邊一直往左移動,但是這個不一樣的地方是,我們每一個元素都有這個進場動畫和離場動畫,我們根本不用關(guān)心它是第幾個元素,你只管輪播就是。
很簡單,我們自己實現(xiàn)一個transtition
的效果就好啦,主要做的是以下兩點
xx-enter-active
動畫
xx-leave-active
, 注意要讓動畫播完才消失
function hide(el){
el.className = el.className.replace(' slide-enter-active','')
el.className += ' slide-leave-active' el.addEventListener('animationend',animationEvent)
} function animationEvent(e){
e.target.className = e.target.className.replace(' slide-leave-active','')
e.target.style.display = 'none' e.target.removeEventListener('animationend',animationEvent)
} function show(el){
el.style.display = 'flex' el.className += ' slide-enter-active' }
這里我們使用了animationend
來監(jiān)聽動畫結(jié)束,注意這里每次從新添加類的時候需要重新添加監(jiān)聽器,不然會無法監(jiān)聽。如果不使用這個方法你可以使用定時器的方式來移除leave-active類。
function hide(el){
el.className = el.className.replace(' slide-enter-active','')
el.className += ' slide-leave-active' setTimeout(()=>
{ //動畫結(jié)束后清除class el.className = el.className.replace(' slide-leave-active','')
el.style.display = 'none' }, ANIMATION_TIME) //這個ANIMATION_TIME為你在css中動畫執(zhí)行的時間 }
.slide-enter-active{ position: absolute; animation: slideIn ease .5s forwards;
} .slide-leave-active{ position: absolute; animation: slideOut ease .5s forwards;
} @keyframes slideIn {
0%{ transform: translateX(100%);
}
100%{ transform: translateX(0);
}
} @keyframes slideOut {
0%{ transform: translateX(0);
}
100%{ transform: translateX(-100%);
}
}
需要注意的是這里的 forwards
屬性,這個屬性表示你的元素狀態(tài)將保持動畫后的狀態(tài),如果不設(shè)置的話,動畫跑完一遍,你的元素本來執(zhí)行了離開動畫,執(zhí)行完以后會回來中央位置杵著。這個時候你會問了,上面的代碼不是寫了,動畫執(zhí)行完就隱藏元素嗎?
如果你使用上面的setTimeout來命令元素執(zhí)行完動畫后消失,那么可能會有一瞬間的閃爍,因為實際業(yè)務(wù)中,你的代碼可能比較復(fù)雜,setTimeout沒法在那么精準的時間內(nèi)執(zhí)行。保險起見,就讓元素保持動畫離開的最后狀態(tài),即translateX(-100%)
。此時元素已經(jīng)在屏幕外了,不用關(guān)心它的表現(xiàn)了
很簡單,我們進一個新元素的時候同時移除舊元素即可,兩者同時執(zhí)行進場和離場動畫即可。
function autoPlay(){
setTimeout(()=>{
toggleShow(新元素, 舊元素) this.autoPlay()
},DURATION) //DURATION為動畫間隔時間 } function toggleShow(newE,oldE){ //舊ele和新ele同時動畫 hide(oldE)
show(newE)
}
手機UI中的交互是保持產(chǎn)品鮮活生命力的源動力。好的交互可以幫助用戶快速地獲得反饋,認知布局,增強體驗感和沉浸感。
手機UI中的交互是保持產(chǎn)品鮮活生命力的源動力。好的交互可以幫助用戶快速地獲得反饋,認知布局,增強體驗感和沉浸感。這里為大家整理了一些優(yōu)秀并富有創(chuàng)意的交互作品,為你的產(chǎn)品設(shè)計注入靈感。
--手機appUI設(shè)計--
藍藍設(shè)計( www.yvirxh.cn )是一家專注而深入的界面設(shè)計公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計、BS界面設(shè)計 、 cs界面設(shè)計 、 ipad界面設(shè)計 、 包裝設(shè)計 、 圖標定制 、 用戶體驗 、交互設(shè)計、 網(wǎng)站建設(shè) 、平面設(shè)計服務(wù)
更多精彩文章:
寫在前面
在平時的設(shè)計過程當中,你可能會有這樣的疑惑,為什么在大部分APP中,當單個按鈕和多個按鈕同時存在時,最重要的按鈕一般都會放置在頁面的右側(cè)呢?如果最重要的按鈕放在左側(cè)又有什么問題呢?按鈕放在右側(cè)的原因是什么呢?它又有什么理論依據(jù)呢?接下來帶著這些疑問,開始我們今天所要介紹的內(nèi)容:交互心理學(xué)之古騰堡原則
古騰堡原則的起源
古騰堡原則是由14世紀西方活字印刷術(shù)的發(fā)明人約翰·古騰堡提出,早在20世紀50年代,他在設(shè)計報紙的過程中,提出了一項原則,認為人的閱讀方式應(yīng)該是遵循某種習(xí)慣進行的,就像讀書一樣,由左到右,從上到下。這其中蘊含著什么信息呢?經(jīng)過研究最終得出被后人所熟知的結(jié)論:古騰堡原則,并附上了一張圖,名為「古騰堡圖」。古騰堡圖將畫面所呈現(xiàn)的內(nèi)容分成四個象限:
1、第一視覺區(qū)(POA):左上方,用戶首先注意到的地方
2、強休息區(qū)(SFA):右上方,較少被注意到
3、弱休息區(qū)(WFA):左下方,最少被注意到
4、終端視覺區(qū)(TA):右下方,視覺流終點
從圖中可以看出,用戶視線很自然的會從第一視覺區(qū)開始,逐漸移動到終端休息區(qū)。整個閱讀過程視線都會沿著一條方向軸開始從左到右瀏覽。用戶會更容易關(guān)注到頁面的開始與結(jié)束區(qū)域,而中間的段落則很少被關(guān)注到。古騰堡揭示了一個實用的視覺軌跡規(guī)律:閱讀引力是從上到下,從左到右。
遵循古騰堡原則把關(guān)鍵信息放在左上角、中間和右下角,能夠更好的體現(xiàn)元素的重要性。例如:我們平時所看到的頁面彈窗、各種證明文件和合同文件等等。
古騰堡圖通過對設(shè)計元素的重量與元素布局和組成方式進行調(diào)和,指導(dǎo)眼睛的運動軌跡。讓用戶迅速獲取有價值的信息,同時用戶對信息的熟悉程度也是影響眼睛運動軌跡的因素之一。
而隨著互聯(lián)網(wǎng)的興起,古騰堡原則也逐漸被應(yīng)用到APP設(shè)計和網(wǎng)頁設(shè)計當中。接下來讓我們來看看他在界面中的實際應(yīng)用。
在設(shè)計中的應(yīng)用
1.1 底部單個按鈕
這種形式在引導(dǎo)用戶操作的頁面中最為常見,為了能夠保證用戶對內(nèi)容進行閱讀,所以將按鈕擺放在頁面底部,內(nèi)容放在頂部,這樣的擺放即符合用戶由上到下的閱讀習(xí)慣又達到了產(chǎn)品預(yù)期的目標。
1.2 底部垂直雙按鈕
上面我們提到了單個按鈕的擺放思路,接下來看一下垂直雙按鈕的擺放思路是怎么樣的。如果一個界面上同時存在兩個優(yōu)先級不同的按鈕,并且產(chǎn)品希望用戶對每一個按鈕都有足夠的關(guān)注度,那么垂直擺放是最佳選擇,雖然垂直雙按鈕在樣式上做了區(qū)分,但用戶同樣會停留一段時間將按鈕的內(nèi)容進行對比思考。
那么,按照古騰堡原則,重要的按鈕應(yīng)該放在頁面最底部,原則上它應(yīng)該是這樣的:
仔細觀察上圖,有沒有發(fā)現(xiàn)淺色按鈕很容易被忽略掉,這樣就違背了產(chǎn)品要保證每一個按鈕都要有足夠關(guān)注度的初衷,所以我們要違背古騰堡原則來滿足業(yè)務(wù)需求,正如我們所看到的微信授權(quán)頁面一樣,
為了保證「同意」與「拒絕」這兩個獨立的按鈕能夠被用戶足夠的重視,并且其中的任意一個按鈕不會被輕易的忽略掉,這里將「同意」按鈕顏色加重,并且放在「拒絕」按鈕之上,讓眼睛原本垂直向下的運動軌跡產(chǎn)生回流的變化。
小結(jié)
原則是設(shè)計的基礎(chǔ),并非一成不變,要合理權(quán)衡設(shè)計原則與產(chǎn)品目標之間的關(guān)系。
2、頂部按鈕分析
由于頂部導(dǎo)航欄空間有限,導(dǎo)致按鈕相對較小,并且不便于點擊操作,所以這類頂部按鈕適用于修改內(nèi)容的編輯頁面,即可以避免誤觸,又可以讓用戶關(guān)注內(nèi)容本身。關(guān)鍵按鈕至于頂部,還可以縮短用戶眼睛的運動路徑,讓用戶更容易注意到其狀態(tài)的變化狀態(tài)。
小結(jié)
頂部按鈕更關(guān)注可編輯的內(nèi)容區(qū)域,并非按鈕。而底部按鈕則更關(guān)注按鈕本身。并非內(nèi)容。
3、水平按鈕分析
除了上面提到的頂部按鈕和底部按鈕,還有水平擺放的按鈕,比如淘寶詳情頁、京東詳情頁、網(wǎng)易嚴選詳情頁的「加入購物車」和「立即購買」按鈕,界面中的「立即購買」按鈕都放在了右下角,結(jié)合古騰堡原則的視覺終點說明,右下角為視覺終端區(qū)域,即視覺最終停留的位置,所以他們都將與轉(zhuǎn)化率密切相關(guān)的「立即購買」按鈕放在了界面的右下角,讓用戶更容易關(guān)注到。
再比如比較常見的「取消」和「確認」彈窗樣式,通常是在需要讓用戶確認某種操作行為時出現(xiàn),有可能是提交表單、協(xié)議授權(quán)、獲取用戶信息等等,為了防止用戶誤操作,這也是提升產(chǎn)品體驗的小細節(jié)。
平常我們所看到的彈窗,推薦按鈕都是在右側(cè),那么將推薦按鈕放在左側(cè)會怎么樣?如下圖所示:
不難看出推薦按鈕放在右側(cè)后,視覺在水平方向軸上產(chǎn)生了回流。
彈窗的目的是想讓用戶點擊「確認」按鈕,如果將「確認」放在左側(cè),根據(jù)古騰堡原則,用戶的視線會不由自主的向右側(cè)移動,也就是「取消」按鈕的位置,想要回到左側(cè)「確認」按鈕位置就需要移動視線,并且眼睛的運動軌跡會在水平方向軸上來回的往復(fù)運動,無形中增加了用戶選擇時長。如果將「確認」放在右側(cè),「取消」放在左側(cè)則可以為用戶提高操作效率。
在實際產(chǎn)品中的應(yīng)用案例:
小結(jié)
當產(chǎn)品想要讓用戶進行某種操作時,主要按鈕放在右邊
總結(jié)
1、古騰堡圖第一視覺區(qū),強休息區(qū),弱休息區(qū),終端視覺區(qū)
2、原則是設(shè)計的基礎(chǔ),并非一成不變,要合理權(quán)衡設(shè)計原則與產(chǎn)品目標之間的關(guān)系
3、頂部按鈕更關(guān)注可編輯的內(nèi)容區(qū)域,并非按鈕。而底部按鈕則更關(guān)注按鈕本身。并非內(nèi)容
4、當產(chǎn)品想要讓用戶進行某種操作時,主要按鈕放在右邊
文章來源:UI中國 作者:Coldrain1
藍藍設(shè)計( www.yvirxh.cn )是一家專注而深入的界面設(shè)計公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計、BS界面設(shè)計 、 cs界面設(shè)計 、 ipad界面設(shè)計 、 包裝設(shè)計 、 圖標定制 、 用戶體驗 、交互設(shè)計、 網(wǎng)站建設(shè) 、平面設(shè)計服務(wù)
垂直居中基本上是入門 CSS 必須要掌握的問題了,我們肯定在各種教程中都看到過“CSS 垂直居中的 N 種方法”,通常來說,這些方法已經(jīng)可以滿足各種使用場景了,然而當我們碰到了需要使用某些特殊字體進行混排、或者使文字對齊圖標的情況時,也許會發(fā)現(xiàn),無論使用哪種垂直居中的方法,總是感覺文字向上或向下偏移了幾像素,不得不專門對它們進行位移,為什么會出現(xiàn)這種情況呢?
下圖是一個使用各種常見的垂直居中的方法來居中文字的示例,其中涉及到不同字體的混排,可以看出,雖然這里面用了幾種常用的垂直居中的方法,但是在實際的觀感上這些文字都沒有恰好垂直居中,有些文字看起來比較居中,而有些文字則偏移得很厲害。
在線查看:CodePen(字體文件直接引用了谷歌字體,如果沒有效果需要注意網(wǎng)絡(luò)情況)
通過設(shè)置vertical-align:middle
對文字進行垂直居中時,父元素需要設(shè)置font-size: 0
,因為vertical-align:middle
是將子元素的中點與父元素的baseline + x-height / 2
的位置進行對齊的,設(shè)置字號為 0 可以保證讓這些線的位置都重合在中點。
我們用鼠標選中這些文字,就能發(fā)現(xiàn)選中的區(qū)域確實是在父層容器里垂直居中的,那么為什么文字卻各有高低呢?這里就涉及到了字體本身的構(gòu)造和相關(guān)的度量值。
這里先提出一個問題,我們在 CSS 中給文字設(shè)置了 font-size
,這個值實際設(shè)置的是字體的什么屬性呢?
下面的圖給出了一個示例,文字所在的標簽均為 span
,對每種字體的文字都設(shè)置了紅色的 outline
以便觀察,且設(shè)有 line-height: normal
。從圖中可以看出,雖然這些文字的字號都是 40px,但是他們的寬高都各不相同,所以字號并非設(shè)置了文字實際顯示的大小。
為了解答這個問題,我們需要對字體進行深入了解,以下這些內(nèi)容是西文字體的相關(guān)概念。首先一個字體會有一個 EM Square(也被稱為 UPM、em、em size)[4],這個值最初在排版中表示一個字體中大寫 M 的寬度,以這個值構(gòu)成一個正方形,那么所有字母都可以被容納進去,此時這個值實際反映的就成了字體容器的高度。在金屬活字中,這個容器就是每個字符的金屬塊,在一種字體里,它們的高度都是統(tǒng)一的,這樣每個字模都可以放入印刷工具中并進行排印。在數(shù)碼排印中,em 是一個被設(shè)置了大小的方格,計量單位是一種相對單位,會根據(jù)實際字體大小縮放,例如 1000 單位的字體設(shè)置了 16pt 的字號,那么這里 1000 單位的大小就是 16pt。Em 在 OpenType 字體中通常為 1000 ,在 TrueType 字體中通常為 1024 或 2048(2 的 n 次冪)。
金屬活字,圖片來自 http://designwithfontforge.com/en-US/The_EM_Square.html
字體本身還有很多概念和度量值(metrics),這里介紹幾個常見的概念,以維基百科的這張圖為例(下面的度量值的計量單位均為基于 em 的相對單位):
接下來我們在 FontForge 軟件里看看這些值的取值,這里以 Arial
字體給出一個例子:
從圖中可以看出,在 General 菜單中,Arial 的 em size 是 2048,字體的 ascent 是1638,descent 是410,在 OS/2 菜單的 Metrics 信息中,可以得到 capital height 是 1467,x height 為 1062,line gap 為 67。
然而這里需要注意,盡管我們在 General 菜單中得到了 ascent 和 descent 的取值,但是這個值應(yīng)該僅用于字體的設(shè)計,它們的和永遠為 em size;而計算機在實際進行渲染的時候是按照 OS/2 菜單中對應(yīng)的值來計算,一般操作系統(tǒng)會使用 hhea(Horizontal Header Table)表的 HHead Ascent 和 HHead Descent,而 Windows 是個特例,會使用 Win Ascent 和 Win Descent。通常來說,實際用于渲染的 ascent 和 descent 取值要比用于字體設(shè)計的大,這是因為多出來的區(qū)域通常會留給注音符號或用來控制行間距,如下圖所示,字母頂部的水平線即為第一張圖中 ascent 高度 1638,而注音符號均超過了這個區(qū)域。根據(jù)資料的說法[5],在一些軟件中,如果文字內(nèi)容超過用于渲染的 ascent 和 descent,就會被截斷,不過我在瀏覽器里實驗后發(fā)現(xiàn)瀏覽器并沒有做這個截斷(Edge 86.0.608.0 Canary (64 bit), MacOS 10.15.6)。
在本文中,我們將后面提到的 ascent 和 descent 均認為是 OS/2 選項中讀取到的用于渲染的 ascent 和 descent 值,同時我們將 ascent + descent 的值叫做 content-area。
理論上一個字體在 Windows 和 MacOS 上的渲染應(yīng)該保持一致,即各自系統(tǒng)上的 ascent 和 descent 應(yīng)該相同,然而有些字體在設(shè)計時不知道出于什么原因,導(dǎo)致其確實在兩個系統(tǒng)中有不同的表現(xiàn)。以下是 Roboto 的例子:
Differences between Win and HHead metrics cause the font to be rendered differently on Windows vs. iOS (or Mac I assume) · Issue #267 · googlefonts/roboto
那么回到本節(jié)一開始的問題,CSS 中的font-size
設(shè)置的值表示什么,想必我們已經(jīng)有了答案,那就是一個字體 em size 對應(yīng)的大小;而文字在設(shè)置了line-height: normal
時,行高的取值則為 content-area + line-gap,即文本實際撐起來的高度。
知道了這些,我們就不難算出一個字體的顯示效果,上面 Arial 字體在line-height: normal
和font-size: 100px
時撐起的高度為(1854 + 434 + 67) / 2048 * 100px = 115px
。
在實驗中發(fā)現(xiàn),對于一個行內(nèi)元素,鼠標拉取的 selection 高度為當前行line-height
最高的元素值。如果是塊狀元素,當line-height
的值為大于 content-area 時,selection 高度為line-height
,當其小于等于 content-area 時,其高度為 content-area 的高度。
在中間插一個問題,我們應(yīng)該都使用過 line-height
來給文字進行垂直居中,那么 line-height
實際是以字體的哪個部分的中點進行計算呢?為了驗證這個問題,我新建了一個很有“設(shè)計感”的字體,em size 設(shè)為 1000,ascent 為 800,descent 為 200,并對其分別設(shè)置了正常的和比較夸張的 metrics:
上面圖中左邊是 FontForge 里設(shè)置的 metrics,右邊是實際顯示效果,文字字號設(shè)為 100px,四個字母均在父層的 flex 布局下垂直居中,四個字母的 line-height
分別為 0、1em、normal、3em,紅色邊框是元素的 outline
,黃色背景是鼠標選取的背景。由上面兩張圖可以看出,字體的 metrics 對文字渲染位置的影響還是很大的。同時可以看出,在設(shè)置 line-height
時,雖然 line gap 參與了撐起取值為 normal
的空間,但是不參與文字垂直居中的計算,即垂直居中的中點始終是 content-area 的中點。
我們又對字體進行了微調(diào),使其 ascent 有一定偏移,這時可以看出 1em 行高的文字 outline 恰好在正中間,因此可以得出結(jié)論:在瀏覽器進行渲染時,em square 總是相對于 content-area 垂直居中。
說完了字體構(gòu)造,又回到上一節(jié)的問題,為什么不同字體文字混排的時候進行垂直居中,文字各有高低呢?
在這個問題上,本文給出這樣一個結(jié)論,那就是因為不同字體的各項度量值均不相同,在進行垂直居中布局時,content-area 的中點與視覺的中點不統(tǒng)一,因此導(dǎo)致實際看起來存在位置偏移,下面這張圖是 Arial 字體的幾個中線位置:
從圖上可以看出來,大寫字母和小寫字母的視覺中線與整個字符的中線還是存在一定的偏移的。這里我沒有找到排版相關(guān)學(xué)科的定論,究竟以哪條線進行居中更符合人眼觀感的居中,以我個人的觀感來看,大寫字母的中線可能看起來更加舒服一點(尤其是與沒有小寫字母的內(nèi)容進行混排的時候)。
需要注意一點,這里選擇的 Arial 這個字體本身的偏移比較少,所以使用時整體感覺還是比較居中的,這并不代表其他字體也都是這樣。
對于中文字體,本身的設(shè)計上沒有基線、升部、降部等說法,每個字都在一個方形盒子中。但是在計算機上顯示時,也在一定程度上沿用了西文字體的概念,通常來說,中文字體的方形盒子中文字體底端在 baseline 和 descender 之間,頂端超出一點 ascender,而標點符號正好在 baseline 上。
我們已經(jīng)了解了字體的相關(guān)概念,那么如何解決在使用字體時出現(xiàn)的偏移問題呢?
通過上面的內(nèi)容可以知道,文字顯示的偏移主要是視覺上的中點和渲染時的中點不一致導(dǎo)致的,那么我們只要把這個不一致修正過來,就可以實現(xiàn)視覺上的居中了。
為了實現(xiàn)這個目標,我們可以借助 vertical-align
這個屬性來完成。當 vertical-align
取值為數(shù)值的時候,該值就表示將子元素的基線與父元素基線的距離,其中正數(shù)朝上,負數(shù)朝下。
這里介紹的方案,是把某個字體下的文字通過計算設(shè)置 vertical-align
的數(shù)值偏移,使其大寫字母的視覺中點與用于計算垂直居中的點重合,這樣字體本身的屬性就不再影響居中的計算。
具體我們將通過以下的計算方法來獲?。菏紫任覀冃枰阎斍白煮w的 em-size,ascent,descent,capital height 這幾個值(如果不知道 em-size,也可以提供其他值與 em-size 的比值),以下依然以 Arial 為例:
const emSize = 2048; const ascent = 1854; const descent = 434; const capitalHeight = 1467;
// 計算前需要已知給定的字體大小 const fontSize = FONT_SIZE; // 根據(jù)文字大小,求得文字的偏移 const verticalAlign = ((ascent - descent - capitalHeight) / emSize) * fontSize; return ( <span style={{ fontFamily: FONT_FAMILY, fontSize }}> <span style={{ verticalAlign }}>TEXT</span> </span> )
由此設(shè)置以后,外層 span 將表現(xiàn)得像一個普通的可替換元素參與行內(nèi)的布局,在一定程度上無視字體 metrics 的差異,可以使用各種方法對其進行垂直居中。
由于這種方案具有固定的計算步驟,因此可以根據(jù)具體的開發(fā)需求,將其封裝為組件、使用 CSS 自定義屬性或使用 CSS 預(yù)處理器對文本進行處理,通過傳入字體信息,就能修正文字垂直偏移。
雖然上述的方案可以在一定程度上解決文字垂直居中的問題,但是在實際使用中還存在著不方便的地方,我們需要在使用字體之前就知道字體的各項 metrics,在自定義字體較少的情況下,開發(fā)者可以手動使用 FontForge 等工具查看,然而當字體較多時,挨個查看還是比較麻煩的。
目前的一種思路是我們可以使用 Canvas 獲取字體的相關(guān)信息,如現(xiàn)在已經(jīng)有開源的獲取字體 metrics 的庫 FontMetrics.js。它的核心思想是使用 Canvas 渲染對應(yīng)字體的文字,然后使用 getImageData
對渲染出來的內(nèi)容進行分析。如果在實際項目中,這種方案可能導(dǎo)致潛在的性能問題;而且這種方式獲取到的是渲染后的結(jié)果,部分字體作者在構(gòu)建字體時并沒有嚴格將設(shè)計的 metrics 和字符對應(yīng),這也會導(dǎo)致獲取到的 metrics 不夠準確。
另一種思路是直接解析字體文件,拿到字體的 metrics 信息,如 opentype.js 這個項目。不過這種做法也不夠輕量,不適合在實際運行中使用,不過可以考慮在打包過程中自動執(zhí)行這個過程。
此外,目前的解決方案更多是偏向理論的方法,當文字本身字號較小的情況下,瀏覽器可能并不能按照預(yù)期的效果渲染,文字會根據(jù)所處的 DOM 環(huán)境不同而具有 1px 的偏移[9]。
CSS Houdini 提出了一個 Font Metrics 草案[6],可以針對文字渲染調(diào)整字體相關(guān)的 metrics。從目前的設(shè)計來看,可以調(diào)整 baseline 位置、字體的 em size,以及字體的邊界大?。?content-area)等配置,通過這些可以解決因字體的屬性導(dǎo)致的排版問題。
[Exposed=Window] interface FontMetrics {
readonly attribute double width;
readonly attribute FrozenArray<double> advances;
readonly attribute double boundingBoxLeft;
readonly attribute double boundingBoxRight;
readonly attribute double height;
readonly attribute double emHeightAscent;
readonly attribute double emHeightDescent;
readonly attribute double boundingBoxAscent;
readonly attribute double boundingBoxDescent;
readonly attribute double fontBoundingBoxAscent;
readonly attribute double fontBoundingBoxDescent;
readonly attribute Baseline dominantBaseline;
readonly attribute FrozenArray<Baseline> baselines;
readonly attribute FrozenArray<Font> fonts;
};
從 https://ishoudinireadyyet.com/ 這個網(wǎng)站上可以看到,目前 Font Metrics 依然在提議階段,還不能確定其 API 具體內(nèi)容,或者以后是否會存在這一個特性,因此只能說是一個在未來也許可行的文字排版處理方案。
文本垂直居中的問題一直是 CSS 中最常見的問題,但是卻很難引起注意,我個人覺得是因為我們常用的微軟雅黑、蘋方等字體本身在設(shè)計上比較規(guī)范,在通常情況下都顯得比較居中。但是當一個字體不是那么“規(guī)范”時,傳統(tǒng)的各種方法似乎就有點無能為力了。
本文分析了導(dǎo)致了文字偏移的因素,并給出尋找文字垂直居中位置的方案。
由于涉及到 IFC 的問題本身就很復(fù)雜[7],關(guān)于內(nèi)聯(lián)元素使用 line-height
與 vertical-align
進行居中的各種小技巧因為與本文不是強相關(guān),所以在文章內(nèi)也沒有提及,如果對這些內(nèi)容比較感興趣,也可以通過下面的參考資料尋找一些相關(guān)介紹。
藍藍設(shè)計( www.yvirxh.cn )是一家專注而深入的界面設(shè)計公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計、BS界面設(shè)計 、 cs界面設(shè)計 、 ipad界面設(shè)計 、 包裝設(shè)計 、 圖標定制 、 用戶體驗 、交互設(shè)計、 網(wǎng)站建設(shè) 、平面設(shè)計服務(wù)
ECMAScript模塊(簡稱ES模塊)是一種JavaScript代碼重用的機制,于2015年推出,一經(jīng)推出就受到前端開發(fā)者的喜愛。在2015之年,JavaScript 還沒有一個代碼重用的標準機制。多年來,人們對這方面的規(guī)范進行了很多嘗試,導(dǎo)致現(xiàn)在有多種模塊化的方式。
你可能聽說過AMD模塊,UMD,或CommonJS,這些沒有孰優(yōu)孰劣。最后,在ECMAScript 2015中,ES 模塊出現(xiàn)了。
我們現(xiàn)在有了一個“正式的”模塊系統(tǒng)。
理論上,ES 模塊應(yīng)該在所有JavaScript環(huán)境中。實際上,ES 模塊的主要應(yīng)用還是在瀏覽器上。
2020年5月,Node.js v12.17.0 增加了在不使用標記前提下對ECMAScript模塊的支持。 這意味著我們現(xiàn)在可以在Node.js
中使用import
和export
,而無需任何其他命令行標志。
ECMAScript模塊要想在任何JavaScript環(huán)境通用,可能還需要很長的路要走,但方向是正確的。
ES 模塊是一個簡單的文件,我們可以在其中聲明一個或多個導(dǎo)出。以下面utils.js
為例:
// utils.js export function funcA() { return "Hello named export!";
} export default function funcB() { return "Hello default export!";
}
這里有兩個導(dǎo)出。
第一個是命名導(dǎo)出,后面是export default
,表示為默認導(dǎo)出。
假設(shè)我們的項目文件夾中有一個名為utils.js
的文件,我們可以將這個模塊提供的對象導(dǎo)入到另一個文件中。
假設(shè)我們在項目文中還有一個Consumer.js
的文件。 要導(dǎo)入utils.js
公開的函數(shù),我們可以這樣做:
// consumer.js import { funcA } from "./util.js";
這種對應(yīng)我們的命名導(dǎo)入方式.
如果我們要導(dǎo)入 utils.js
中的默認導(dǎo)出也就是 funcB
方法,我們可以這樣做:
// consumer.js import { funcA } from "./util.js";
當然,我們可以導(dǎo)入同時導(dǎo)入命名和默認的:
// consumer.js import funcB, { funcA } from "./util.js";
funcB();
funcA();
我們也可以用星號導(dǎo)入整個模塊:
import * as myModule from './util.js';
myModule.funcA();
myModule.default();
注意,這里要使用默認到處的方法是使用 default()
而不是 funcB()
。
從遠程模塊導(dǎo)入:
import { createStore } from "https://unpkg.com/redux@4.0.5/es/redux.mjs"; const store = createStore(/* do stuff */)
現(xiàn)代瀏覽器支持ES模塊,但有一些警告。 要使用模塊,需要在 script
標簽上添加屬性 type
, 對應(yīng)值 為 module
。
<html lang="en"> <head> <meta charset="UTF-8"> <title>ECMAScript modules in the browser</title>
</head> <body> <p id="el">The result is:
</p> </body> <script type="module"> import { appendResult } from "./myModule.js"; const el = document.getElementById("el");
appendResult(el);
appendResult(el);
appendResult(el);
appendResult(el);
appendResult(el); </script> </html>
myModule.js
內(nèi)容如下:
export function appendResult(element) { const result = Math.random();
element.innerText += result;
}
ES 模塊是靜態(tài)的,這意味著我們不能在運行時更改導(dǎo)入。隨著2020年推出的動態(tài)導(dǎo)入(dynamic imports),我們可以動態(tài)加載代碼來響應(yīng)用戶交互(webpack早在ECMAScript 2020推出這個特性之前就提供了動態(tài)導(dǎo)入)。
考慮下面的代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8">
<title>Dynamic imports</title> </head> <body> <button id="btn">Load!</button> </body> <script src="loader.js"></script> </html>
再考慮一個帶有兩個導(dǎo)出的JavaScript模塊
// util.js export function funcA() { console.log("Hello named export!");
} export default function funcB() { console.log("Hello default export!");
}
為了動態(tài)導(dǎo)入 util.js 模塊,我們可以點擊按鈕在去導(dǎo)入:
/ loader.js
const btn = document.getElementById("btn");
btn.addEventListener("click", () => { // loads named export import("./util.js").then(({ funcA }) => {
funcA();
});
});
這里使用解構(gòu)的方式,取出命名導(dǎo)出 funcA
方法:
({ funcA }) => {}
ES模塊實際上是JavaScript對象:我們可以解構(gòu)它們的屬性以及調(diào)用它們的任何公開方法。
要使用動態(tài)導(dǎo)入的默認方法,可以這樣做
// loader.js const btn = document.getElementById("btn");
btn.addEventListener("click", () => { import("./util.js").then((module) => { module.default();
});
});
當作為一個整體導(dǎo)入一個模塊時,我們可以使用它的所有導(dǎo)出
// loader.js const btn = document.getElementById("btn");
btn.addEventListener("click", () =>
{ // loads entire module // uses everything import("./util.js").then((module) => { module.funcA(); module.default();
});
});
還有另一種用于動態(tài)導(dǎo)入的常見樣式,如下所示:
const loadUtil = () => import("./util.js"); const btn = document.getElementById("btn");
btn.addEventListener("click", () => { // });
loadUtil
返回的是一個 promise,所以我們可以這樣操作
const loadUtil = () => import("./util.js"); const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
loadUtil().then(module => { module.funcA(); module.default();
})
})
動態(tài)導(dǎo)入看起來不錯,但是它們有什么用呢?
使用動態(tài)導(dǎo)入,我們可以拆分代碼,并只在適當?shù)臅r候加載重要的代碼。在 JavaScript 引入動態(tài)導(dǎo)入之前,這種模式是webpack(模塊綁定器)獨有的。
像React
和Vue
通過動態(tài)導(dǎo)入代碼拆分來加載響應(yīng)事件的代碼塊,比如用戶交互或路由更改。
假設(shè)我們項目有一個 person.json
文件,內(nèi)容如下:
{ "name": "Jules", "age": 43 }
現(xiàn)在,我們需要動態(tài)導(dǎo)入該文件以響應(yīng)某些用戶交互。
因為 JSON 文件不是一個方法,所以我們可以使用默認導(dǎo)出方式:
const loadPerson = () => import('./person.json'); const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
loadPerson().then(module => { const { name, age } = module.default; console.log(name, age);
});
});
這里我們使用解構(gòu)的方式取出 name
和 age
:
const { name, age } = module.default;
因為 import()
語句返回是一個 Promise,所以我們可以使用 async/await
:
const loadUtil = () => import("./util.js"); const btn = document.getElementById("btn");
btn.addEventListener("click", async () => { const utilsModule = await loadUtil();
utilsModule.funcA();
utilsModule.default();
})
使用import()
導(dǎo)入模塊時,可以按照自己的意愿命名它,但要調(diào)用的方法名保持一致:
import("./util.js").then((module) => { module.funcA(); module.default();
});
或者:
import("./util.js").then((utilModule) => {
utilModule.funcA();
utilModule.default();
});
原文:https://www.valentinog.com/bl...
代碼部署后可能存在的BUG沒法實時知道,事后為了解決這些BUG,花了大量的時間進行l(wèi)og 調(diào)試,這邊順便給大家推薦一個好用的BUG監(jiān)控工具 Fundebug。
TypeScript 是一種由微軟開發(fā)的自由和開源的編程語言。它是 JavaScript 的一個超集,而且本質(zhì)上向這個語言添加了可選的靜態(tài)類型和基于類的面向?qū)ο缶幊獭?
本文阿寶哥將分享這些年在學(xué)習(xí) TypeScript 過程中,遇到的 10 大 “奇怪” 的符號。其中有一些符號,阿寶哥第一次見的時候也覺得 “一臉懵逼”,希望本文對學(xué)習(xí) 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 調(diào)用函數(shù)時忽略 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 代碼中,! 非空斷言操作符被移除了,所以在瀏覽器中執(zhí)行以上代碼,在控制臺會輸出 undefined。
二、?. 運算符
TypeScript 3.7 實現(xiàn)了呼聲最高的 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,這樣就可以立即停止某些表達式的運行。你可能已經(jīng)想到可以使用 ?. 來替代很多使用 && 執(zhí)行空檢查的代碼:
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 或空字符串來說,并不會出現(xiàn) “短路”。
2.1 可選元素訪問
可選鏈除了支持可選屬性的訪問之外,它還支持可選元素的訪問,它的行為類似于可選屬性的訪問,只是可選元素的訪問允許我們訪問非標識符的屬性,比如任意字符串、數(shù)字索引和 Symbol:
function tryGetArrayElement<T>(arr?: T[], index: number = 0) { return arr?.[index];
}
以上代碼經(jīng)過編譯后會生成以下 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 方法中會自動檢測輸入?yún)?shù) arr 的值是否為 null 或 undefined,從而保證了我們代碼的健壯性。
2.2 可選鏈與函數(shù)調(diào)用
當嘗試調(diào)用一個可能不存在的方法時也可以使用可選鏈。在實際開發(fā)過程中,這是很有用的。系統(tǒng)中某個方法不可用,有可能是由于版本不一致或者用戶設(shè)備兼容性問題導(dǎo)致的。函數(shù)調(diào)用時如果被調(diào)用的方法不存在,使用可選鏈可以使表達式自動返回 undefined 而不是拋出一個異常。
可選調(diào)用使用起來也很簡單,比如:
let result = obj.customMethod?.();
該 TypeScript 代碼編譯生成的 ES5 代碼如下:
var result = (_a = obj.customMethod) === null || _a === void 0 ? void 0 : _a.call(obj);
另外在使用可選調(diào)用的時候,我們要注意以下兩個注意事項:
如果存在一個屬性名且該屬性名對應(yīng)的值不是函數(shù)類型,使用 ?. 仍然會產(chǎn)生一個 TypeError 異常。
可選鏈的運算行為被局限在屬性的訪問、調(diào)用以及元素的訪問 —— 它不會沿伸到后續(xù)的表達式中,也就是說可選調(diào)用不會阻止 a?.b / someMethod() 表達式中的除法運算或 someMethod 的方法調(diào)用。
三、?? 空值合并運算符
在 TypeScript 3.7 版本中除了引入了前面介紹的可選鏈 ?. 之外,也引入了一個新的邏輯運算符 —— 空值合并運算符 ??。當左側(cè)操作數(shù)為 null 或 undefined 時,其返回右側(cè)的操作數(shù),否則返回左側(cè)的操作數(shù)。
與邏輯或 || 運算符不同,邏輯或會在左操作數(shù)為 falsy 值時返回右側(cè)操作數(shù)。也就是說,如果你使用 || 來為某些變量設(shè)置默認的值時,你可能會遇到意料之外的行為。比如為 falsy 值(''、NaN 或 0)時。
這里來看一個具體的例子:
const foo = null ?? 'default string'; console.log(foo); // 輸出:"default string" const baz = 0 ?? 42; console.log(baz); // 輸出:0
以上 TS 代碼經(jīng)過編譯后,會生成以下 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());
上述代碼運行后,控制臺會輸出以下結(jié)果:
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
但當使用括號來顯式表明優(yōu)先級時是可行的,比如:
(null || undefined ) ?? "foo"; // 返回 "foo"
3.3 與可選鏈操作符 ?. 的關(guān)系
空值合并運算符針對 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
前面我們已經(jīng)介紹了空值合并運算符的應(yīng)用場景和使用時的一些注意事項,該運算符不僅可以在 TypeScript 3.7 以上版本中使用。當然你也可以在 JavaScript 的環(huán)境中使用它,但你需要借助 Babel,在 Babel 7.8.0 版本也開始支持空值合并運算符。
四、?: 可選屬性
在面向?qū)ο笳Z言中,接口是一個很重要的概念,它是對行為的抽象,而具體如何行動需要由類去實現(xiàn)。 TypeScript 中的接口是一個非常靈活的概念,除了可用于對類的一部分行為進行抽象以外,也常用于對「對象的形狀(Shape)」進行描述。
在 TypeScript 中使用 interface 關(guān)鍵字就可以聲明一個接口:
interface Person {
name: string;
age: number;
} let semlinker: Person = {
name: "semlinker",
age: 33,
};
在以上代碼中,我們聲明了 Person 接口,它包含了兩個必填的屬性 name 和 age。在初始化 Person 類型變量時,如果缺少某個屬性,TypeScript 編譯器就會提示相應(yīng)的錯誤信息,比如:
// 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>
在實際項目開發(fā)過程中,為了提高代碼復(fù)用率,我們可以利用 TypeScript 內(nèi)置的工具類型 Partial<T> 來快速把某個接口類型中定義的屬性變成可選的:
interface PullDownRefreshConfig {
threshold: number;
stop: number;
} /**
* type PullDownRefreshOptions = {
* threshold?: number | undefined;
* stop?: number | undefined;
* }
*/ type PullDownRefreshOptions = Partial<PullDownRefreshConfig>
是不是覺得 Partial<T> 很方便,下面讓我們來看一下它是如何實現(xiàn)的:
/**
* 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> 工具類型是如何實現(xiàn)的:
/**
* Make all properties in T required
*/ type Required<T> = {
[P in keyof T]-?: T[P];
};
原來在 Required<T> 工具類型內(nèi)部,通過 -? 移除了可選屬性中的 ?,使得屬性從可選變?yōu)楸剡x的。
五、& 運算符
在 TypeScript 中交叉類型是將多個類型合并為一個類型。通過 & 運算符可以將現(xiàn)有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性。
type PartialPointX = { x: number; }; type Point = PartialPointX & { y: number; }; let point: Point = {
x: 1,
y: 1 }
在上面代碼中我們先定義了 PartialPointX 類型,接著使用 & 運算符創(chuàng)建一個新的 Point 類型,表示一個含有 x 和 y 坐標的點,然后定義了一個 Point 類型的變量并初始化。
5.1 同名基礎(chǔ)類型屬性的合并
那么現(xiàn)在問題來了,假設(shè)在合并多個類型的過程中,剛好出現(xiàn)某些類型存在相同的成員,但對應(yīng)的類型又不一致,比如:
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 同名非基礎(chǔ)類型屬性的合并
在上面示例中,剛好接口 X 和接口 Y 中內(nèi)部成員 c 的類型都是基本數(shù)據(jù)類型,那么如果是非基本數(shù)據(jù)類型的話,又會是什么情形。我們來看個具體的例子:
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);
以上代碼成功運行后,控制臺會輸出以下結(jié)果:
由上圖可知,在混入多個類型時,若存在相同的成員,且成員類型為非基本數(shù)據(jù)類型,那么是可以成功合并。
六、| 分隔符
在 TypeScript 中聯(lián)合類型(Union Types)表示取值可以為多種類型中的一種,聯(lián)合類型使用 | 分隔每個類型。聯(lián)合類型通常與 null 或 undefined 一起使用:
const sayHello = (name: string | undefined) => { /* ... */ };
以上示例中 name 的類型是 string | undefined 意味著可以將 string 或 undefined 的值傳遞給 sayHello 函數(shù)。
sayHello("semlinker");
sayHello(undefined);
此外,對于聯(lián)合類型來說,你可能會遇到以下的用法:
let num: 1 | 2 = 1; type EventNames = 'click' | 'scroll' | 'mousemove';
示例中的 1、2 或 'click' 被稱為字面量類型,用來約束取值只能是某幾個值中的一個。
6.1 類型保護
當使用聯(lián)合類型時,我們必須盡量把當前值的類型收窄為當前值的實際類型,而類型保護就是實現(xiàn)類型收窄的一種手段。
類型保護是可執(zhí)行運行時檢查的一種表達式,用于確保該類型在一定的范圍內(nèi)。換句話說,類型保護可以保證一個字符串是一個字符串,盡管它的值也可以是一個數(shù)字。類型保護與特性檢測并不是完全不同,其主要思想是嘗試檢測屬性、方法或原型,以確定如何處理值。
目前主要有四種的方式來實現(xiàn)類型保護:
6.1.1 in 關(guān)鍵字
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 關(guān)鍵字
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 關(guān)鍵字
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";
}
七、_ 數(shù)字分隔符
TypeScript 2.7 帶來了對數(shù)字分隔符的支持,正如數(shù)值分隔符 ECMAScript 提案中所概述的那樣。對于一個數(shù)字字面量,你現(xiàn)在可以通過把一個下劃線作為它們之間的分隔符來分組數(shù)字:
const inhabitantsOfMunich = 1_464_301; const distanceEarthSunInKm = 149_600_000; const fileSystemPermission = 0b111_111_000; const bytes = 0b1111_10101011_11110000_00001101;
分隔符不會改變數(shù)值字面量的值,但邏輯分組使人們更容易一眼就能讀懂數(shù)字。以上 TS 代碼經(jīng)過編譯后,會生成以下 ES5 代碼:
"use strict"; var inhabitantsOfMunich = 1464301; var distanceEarthSunInKm = 149600000; var fileSystemPermission = 504; var bytes = 262926349;
7.1 使用限制
雖然數(shù)字分隔符看起來很簡單,但在使用時還是有一些限制。比如你只能在兩個數(shù)字之間添加 _ 分隔符。以下的使用方式是非法的:
// 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
當然你也不能連續(xù)使用多個 _ 分隔符,比如:
// Multiple consecutive numeric separators are not permitted.(6189) 123__456 // Error
7.2 解析分隔符
此外,需要注意的是以下用于解析數(shù)字的函數(shù)是不支持分隔符:
Number()
parseInt()
parseFloat()
這里我們來看一下實際的例子:
Number('123_456') NaN parseInt('123_456') 123 parseFloat('123_456') 123
很明顯對于以上的結(jié)果不是我們所期望的,所以在處理分隔符時要特別注意。當然要解決上述問題,也很簡單只需要非數(shù)字的字符刪掉即可。這里我們來定義一個 removeNonDigits 的函數(shù):
const RE_NON_DIGIT = /[^0-9]/gu; function removeNonDigits(str) {
str = str.replace(RE_NON_DIGIT, ''); return Number(str);
}
該函數(shù)通過調(diào)用字符串的 replace 方法來移除非數(shù)字的字符,具體的使用方式如下:
removeNonDigits('123_456') 123456 removeNonDigits('149,600,000') 149600000 removeNonDigits('1,407,836') 1407836
八、<Type> 語法
8.1 TypeScript 斷言
有時候你會遇到這樣的情況,你會比 TypeScript 更了解某個值的詳細信息。通常這會發(fā)生在你清楚地知道一個實體具有比它現(xiàn)有類型更確切的類型。
通過類型斷言這種方式可以告訴編譯器,“相信我,我知道自己在干什么”。類型斷言好比其他語言里的類型轉(zhuǎn)換,但是不進行特殊的數(shù)據(jù)檢查和解構(gòu)。它沒有運行時的影響,只是在編譯階段起作用。
類型斷言有兩種形式:
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> 語法會感到陌生。其實它沒有什么特別,就像傳遞參數(shù)一樣,我們傳遞了我們想要用于特定函數(shù)調(diào)用的類型。
參考上面的圖片,當我們調(diào)用 identity<Number>(1) ,Number 類型就像參數(shù) 1 一樣,它將在出現(xiàn) T 的任何位置填充該類型。圖中 <T> 內(nèi)部的 T 被稱為類型變量,它是我們希望傳遞給 identity 函數(shù)的類型占位符,同時它被分配給 value 參數(shù)用來代替它的類型:此時 T 充當?shù)氖穷愋?,而不是特定?Number 類型。
其中 T 代表 Type,在定義泛型時通常用作第一個類型變量名稱。但實際上 T 可以用任何有效名稱代替。除了 T 之外,以下是常見泛型變量代表的意思:
K(Key):表示對象中的鍵類型;
V(Value):表示對象中的值類型;
E(Element):表示元素類型。
其實并不是只能定義一個類型變量,我們可以引入希望定義的任何數(shù)量的類型變量。比如我們引入一個新的類型變量 U,用于擴展我們定義的 identity 函數(shù):
function identity <T, U>(value: T, message: U) : T { console.log(message); return value;
} console.log(identity<Number, string>(68, "Semlinker"));
除了為類型變量顯式設(shè)定值之外,一種更常見的做法是使編譯器自動選擇這些類型,從而使代碼更簡潔。我們可以完全省略尖括號,比如:
function identity <T, U>(value: T, message: U) : T { console.log(message); return value;
} console.log(identity(68, "Semlinker"));
對于上述代碼,編譯器足夠聰明,能夠知道我們的參數(shù)類型,并將它們賦值給 T 和 U,而不需要開發(fā)人員顯式指定它們。
九、@XXX 裝飾器
9.1 裝飾器語法
對于一些剛接觸 TypeScript 的小伙伴來說,在第一次看到 @Plugin({...}) 這種語法可能會覺得很驚訝。其實這是裝飾器的語法,裝飾器的本質(zhì)是一個函數(shù),通過裝飾器我們可以方便地定義與對象相關(guān)的元數(shù)據(jù)。
@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 插件的相關(guān)元信息,而 @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() 最終會被轉(zhuǎn)換成普通的方法調(diào)用,它們的調(diào)用結(jié)果最終會以數(shù)組的形式作為參數(shù)傳遞給 __decorate 函數(shù),而在 __decorate 函數(shù)內(nèi)部會以 Device 類作為參數(shù)調(diào)用各自的類型裝飾器,從而擴展對應(yīng)的功能。
9.2 裝飾器的分類
在 TypeScript 中裝飾器分為類裝飾器、屬性裝飾器、方法裝飾器和參數(shù)裝飾器四大類。
9.2.1 類裝飾器
類裝飾器聲明:
declare type ClassDecorator = <TFunction extends Function>(
target: TFunction
) => TFunction | void;
類裝飾器顧名思義,就是用來裝飾類的。它接收一個參數(shù):
target: TFunction - 被裝飾的類
看完第一眼后,是不是感覺都不好了。沒事,我們馬上來個例子:
function Greeter(target: Function): void {
target.prototype.greet = function (): void { console.log("Hello Semlinker!");
};
} @Greeter class Greeting { constructor() { // 內(nèi)部實現(xiàn) }
} let myGreeting = new Greeting();
myGreeting.greet(); // console output: 'Hello Semlinker!';
上面的例子中,我們定義了 Greeter 類裝飾器,同時我們使用了 @Greeter 語法糖,來使用裝飾器。
友情提示:讀者可以直接復(fù)制上面的代碼,在 TypeScript Playground 中運行查看結(jié)果。
9.2.2 屬性裝飾器
屬性裝飾器聲明:
declare type PropertyDecorator = (target:Object,
propertyKey: string | symbol ) => void;
屬性裝飾器顧名思義,用來裝飾類的屬性。它接收兩個參數(shù):
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 函數(shù),來跟蹤用戶對屬性的操作,當代碼成功運行后,在控制臺會輸出以下結(jié)果:
Set: name => semlinker Set: name => kakuqo
9.2.3 方法裝飾器
方法裝飾器聲明:
declare type MethodDecorator = <T>(target:Object, propertyKey: string | symbol,
descriptor: TypePropertyDescript<T>) => TypedPropertyDescriptor<T> | void;
方法裝飾器顧名思義,用來裝飾類的方法。它接收三個參數(shù):
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 參數(shù)裝飾器
參數(shù)裝飾器聲明:
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol,
parameterIndex: number ) => void
參數(shù)裝飾器顧名思義,是用來裝飾函數(shù)參數(shù),它接收三個參數(shù):
target: Object - 被裝飾的類
propertyKey: string | symbol - 方法名
parameterIndex: number - 方法中參數(shù)的索引值
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.
與常規(guī)屬性(甚至使用 private 修飾符聲明的屬性)不同,私有字段要牢記以下規(guī)則:
私有字段以 # 字符開頭,有時我們稱之為私有名稱;
每個私有字段名稱都唯一地限定于其包含的類;
不能在私有字段上使用 TypeScript 可訪問性修飾符(如 public 或 private);
私有字段不能在包含的類之外訪問,甚至不能被檢測到。
10.1 私有字段與 private 的區(qū)別
說到這里使用 # 定義的私有字段與 private 修飾符定義字段有什么區(qū)別呢?現(xiàn)在我們先來看一個 private 的示例:
class Person { constructor(private name: string){}
} let person = new Person("Semlinker"); console.log(person.name);
在上面代碼中,我們創(chuàng)建了一個 Person 類,該類中使用 private 修飾符定義了一個私有屬性 name,接著使用該類創(chuàng)建一個 person 對象,然后通過 person.name 來訪問 person 對象的私有屬性,這時 TypeScript 編譯器會提示以下異常:
Property 'name' is private and only accessible within class 'Person'.(2341)
那如何解決這個異常呢?當然你可以使用類型斷言把 person 轉(zhuǎn)為 any 類型:
console.log((person as any).name);
通過這種方式雖然解決了 TypeScript 編譯器的異常提示,但是在運行時我們還是可以訪問到 Person 類內(nèi)部的私有屬性,為什么會這樣呢?我們來看一下編譯生成的 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}!`);
}
}
以上代碼目標設(shè)置為 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 這兩個方法用于設(shè)置值和獲取值。
藍藍設(shè)計( www.yvirxh.cn )是一家專注而深入的界面設(shè)計公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計、BS界面設(shè)計 、 cs界面設(shè)計 、 ipad界面設(shè)計 、 包裝設(shè)計 、 圖標定制 、 用戶體驗 、交互設(shè)計、 網(wǎng)站建設(shè) 、平面設(shè)計服務(wù)https://github.com/krasimir/l...
如果你必須在同一個瀏覽器中從一個標簽頁發(fā)送消息到另一個標簽頁,你不必用艱難的方式。Local storage bridge在這里讓任務(wù)變得更簡單。
基本使用:
// 發(fā)送 lsbridge.send(‘a(chǎn)pp.message.error’, { error: ‘Out of memory’ });
// 監(jiān)聽 lsbridge.subscribe(‘a(chǎn)pp.message.error’, function(data) { console.log(data); // { error: ‘Out of memory’ } });
Basil.js統(tǒng)一了session、localStorage和cookie,為你提供了一種處理數(shù)據(jù)的直接方法。
基本使用:
let basil = new Basil(options);
basil.set(‘name’, ‘Amy’);
basil.get(‘name’);
basil.remove(‘name’);
basil.reset();
https://github.com/marcuswest...
Store.js像其他東西一樣處理數(shù)據(jù)存儲。但還有更多的功能,它的一個高級特性是讓你更深入地訪問瀏覽器支持。
基本使用:
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
https://github.com/pamelafox/...
它與localStorage API類似。事實上,它是localStorage的一個封裝器,并使用HTML5模擬memcaches函數(shù)。在上面的文檔中發(fā)現(xiàn)更多的功能。
基本使用:
lscache.set(‘name’, ‘Amy’, 5); // 數(shù)據(jù)將在5分鐘后過期 lscache.get(‘name’);
Lockr建立在localStorage API之上。它提供了一些有用的方法來更輕松地處理本地數(shù)據(jù)。
是什么讓你要使用此庫而不是localStorage API?
好吧,localStorage API僅允許你存儲字符串。如果要存儲數(shù)字,則需要先將該數(shù)字轉(zhuǎn)換為字符串。在Lockr中不會發(fā)生這種情況,因為Lockr允許你存儲更多的數(shù)據(jù)類型甚至對象。
基本使用:
Lockr.set(‘name’, ‘Amy’);
Lockr.set(‘a(chǎn)ge’, 28);
Lockr.set(‘books’, [{title: ‘JavaScript’, price: 11.0}, {title: ‘Python’, price: 9.0}]);
https://github.com/arokor/barn
Barn在localStorage之上提供了一個類似Redis的API。如果持久性很重要,那么你將需要這個庫來保持數(shù)據(jù)狀態(tài),以防發(fā)生錯誤。
基本使用:
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
https://github.com/localForag...
這個簡單而快速的庫將通過IndexedDB或WebSQL使用異步存儲來改善Web的脫機體驗。它類似于localStorage,但具有回調(diào)功能。
基本使用:
localforage.setItem(‘name’, ‘Amy’, function(error, value) { // Do something });
localforage.getItem(‘name’, function(error, value) { if (error) { console.log(‘a(chǎn)n error occurs’);
} else { // Do something with the value }
});
很神奇的是它提供中文文檔
https://github.com/jas-/crypt.io
crypt.io使用標準JavaScript加密庫實現(xiàn)安全的瀏覽器存儲。使用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 });
藍藍設(shè)計( www.yvirxh.cn )是一家專注而深入的界面設(shè)計公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計、BS界面設(shè)計 、 cs界面設(shè)計 、 ipad界面設(shè)計 、 包裝設(shè)計 、 圖標定制 、 用戶體驗 、交互設(shè)計、 網(wǎng)站建設(shè) 、平面設(shè)計服務(wù)
藍藍設(shè)計的小編 http://www.yvirxh.cn