摘要:允許我們把水平的代碼回調函數的地獄轉換為豎直的代碼在之前,我們使用或是,現在我們有了這里我們有個,執行成功時調用的函數和失敗時調用的函數。使用的好處使用嵌套的回調函數處理錯誤會很混亂。
es6-參考手冊
該手冊包括ES2015[ES6]的知識點、技巧、建議和每天工作用的代碼段例子。歡迎補充和建議。
var 和 let / const除了var,我們現在有了兩種新的標示符用來存儲值——let和const。與var不同的是,let和const 聲明不會提前到作用域的開頭。(譯注:即不發生聲明提前)
一個使用var的例子:
var snack = "Meow Mix"; function getFood(food) { if (food) { var snack = "Friskies"; return snack; } return snack; } getFood(false); // undefined
然而, 我們把var替換成let,觀察會發生什么:
let snack = "Meow Mix"; function getFood(food) { if (food) { let snack = "Friskies"; return snack; } return snack; } getFood(false); // "Meow Mix"
這種行為變化提示我們,在使用var重構遺留代碼的時候需要小心,盲目的把let替換成var會導致以外的行為
注意: let和const具有塊作用域。因此在它們定義前調用會引發ReferenceError
console.log(x); // ReferenceError: x is not defined let x = "hi";
建議:在遺留代碼中的保留var聲明表示需要小心的重構。在新建代碼庫時,使用let聲明以后會只會發生改變的變量,用const聲明那些以后不會被改變的變量。
譯注:const修飾的基本類型不能改變,而其修飾的對象、函數、數組等引用類型,可以改變內部的值,但不能改變其引用。
用塊(Blocks)替換立即執行函數(IIFEs)立即執行函數常被用作閉包,把變量控制在作用域內。在ES6中,我們可以創建一個塊作用域而且不僅僅是基于函數的作用域。
(function () { var food = "Meow Mix"; }()); console.log(food); // Reference Error Using ES6 Blocks: { let food = "Meow Mix"; }; console.log(food); // Reference Error箭頭函數(Arrow Functions)
通常我們會建立嵌套函數,當我們想保留this上下文作用域的時候(該怎么辦?)。如下就是一個例子:
function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; // Cannot read property "name" of undefined }); };
一個常見的解決方法是利用一個變量存儲這個this的上下文。
function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { var that = this; // Store the context of this return arr.map(function (character) { return that.name + character; }); };
我們也可以傳入這個this的上下文:
function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; }, this); };
還可以用bind綁定這個上下文:
function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; }.bind(this)); };
使用箭頭函數,this的詞法作用域不會被影響,我們可以像以下這樣重寫之前的代碼
function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(character => this.name + character); };
建議: 只要當你想保留this的詞法作用域時,就使用箭頭函數
對于一個簡單返回一個值的函數來說,箭頭函數顯得更加簡潔
var squares = arr.map(function (x) { return x * x }); // Function Expression const arr = [1, 2, 3, 4, 5]; const squares = arr.map(x => x * x); // Arrow Function for terser implementation
建議: 盡可能用箭頭函數替換你的函數定義
字符串(Strings)在ES6中, 標準庫也在不斷的擴充。在這些變化中就有很多方法可以用于字符串,比如.includes()和.repeat()
.includes()var string = "food"; var substring = "foo"; console.log(string.indexOf(substring) > -1);
檢測返回值是否大于-1表示字符串是否存在,我們可以用.includes()替換,它返回一個boolean值。
const string = "food"; const substring = "foo"; console.log(string.includes(substring)); // true.repeat()
function repeat(string, count) { var strings = []; while(strings.length < count) { strings.push(string); } return strings.join(""); }
在ES6中,我們一種更簡潔的實現方法:
// String.repeat(numberOfRepetitions) "meow".repeat(3); // "meowmeowmeow"模板字面量(Template Literals)
(譯注:原文是Template Literals,而非Template Strings)
利用模板字面量,我們可以直接在字符串使用特殊字符而不用轉義它們。
var text = "This string contains "double quotes" which are escaped."; let text = `This string contains "double quotes" which don"t need to be escaped anymore.`;
模板字面量還支持插值,可以輕松完成連接字符串和值的任務:
var name = "Tiger"; var age = 13; console.log("My cat is named " + name + " and is " + age + " years old.");
const name = "Tiger"; const age = 13; console.log(`My cat is named ${name} and is ${age} years old.`);
在ES5中,我們這樣操作多行字符串:
var text = ( "cat " + "dog " + "nickelodeon" );
或者
var text = [ "cat", "dog", "nickelodeon" ].join(" ");
模板字面量可以保留多行字符串,我們無需顯式的放置它們:
let text = ( `cat dog nickelodeon` );
模板字面量可以接受表達式, 比如:
let today = new Date(); let text = `The time and date is ${today.toLocaleString()}`;解構賦值(Destructuring)
解構賦值允許我們從數組或對象中提取出值(甚至深度嵌套的值),并把他們存入變量的簡單語法
解構數組(Destructuring Arrays)var arr = [1, 2, 3, 4]; var a = arr[0]; var b = arr[1]; var c = arr[2]; var d = arr[3]; let [a, b, c, d] = [1, 2, 3, 4]; console.log(a); // 1 console.log(b); // 2解構對象(Destructuring Objects)
var luke = { occupation: "jedi", father: "anakin" }; var occupation = luke.occupation; // "jedi" var father = luke.father; // "anakin" let luke = { occupation: "jedi", father: "anakin" }; let {occupation, father} = luke; console.log(occupation); // "jedi" console.log(father); // "anakin"模塊(Modules)
ES6之前,我們只用如Browserify的庫在客戶端創建模塊,并且需要用到Node.js。利用ES6,我們現在可以直接使用任何類型的模塊(AMD和CommonJS)
CommonJS中的exportsmodule.exports = 1; module.exports = { foo: "bar" }; module.exports = ["foo", "bar"]; module.exports = function bar () {};ES6中的export
在ES6中,提供各種不同類型的exports,我們可以運行如下:
export let name = "David"; export let age = 25;??
輸出對象列表:
function sumTwo(a, b) { return a + b; } function sumThree(a, b, c) { return a + b + c; } export { sumTwo, sumThree };
我們也可以簡單地通過export關鍵字輸出函數、對象和值(等等):
export function sumTwo(a, b) { return a + b; } export function sumThree(a, b, c) { return a + b + c; } And lastly, we can export default bindings: function sumTwo(a, b) { return a + b; } function sumThree(a, b, c) { return a + b + c; } let api = { sumTwo, sumThree };
輸出默認api:
/* Which is the same as * export { api as default }; */
建議:在模塊結束的地方,始終輸出默認的方法。這樣可以清晰地看到接口,并且通過弄清楚輸出值的名稱節省時間。所以在CommonJS模塊中通常輸出一個對象或值。堅持使用這種模式,會使我們的代碼易讀,并且可以輕松的在ES6和CommonJS中進行插補。
ES6ES6 提供提供各種不同的imports,我們輸入一整個文件:
import "underscore";
這里值得注意的是,簡單的輸入一文件會在文件的最頂層執行代碼。和Python類似,我們已經命名了imports:
import { sumTwo, sumThree } from "math/addition";
我們還可以重命名這些已經有名的imports:
import { sumTwo as addTwoNumbers, sumThree as sumThreeNumbers } from "math/addition";
此外,我們可以輸入各種東西(也叫做 namespace import)
import * as util from "math/addition";
最后,我們可以從模塊輸入一列值:
import * as additionUtil from "math/addition"; const { sumTwo, sumThree } = additionUtil;
如下這樣從默認綁定進行輸入
import api from "math/addition"; // 例如: import { default as api } from "math/addition";
雖然最好要保持輸出簡單,但是如果我們需要,我們有時可以混用默認輸入,當我們想如下輸出的時候:
// foos.js export { foo as default, foo1, foo2 };
我們可以如下輸入它們:
import foo, { foo1, foo2 } from "foos";
當使用commonj語法(如React)輸入一個模型的輸出時,我們可以這樣做:
import React from "react"; const { Component, PropTypes } = React;
這個也可以進一步簡化,使用:
import React, { Component, PropTypes } from "react";
注意:輸出的值是綁定,不是引用。因此,綁定的值發生變化會影響輸出的模型中的值。避免修改這些輸出值的公共接口。參數(Parameters)
在ES5中,我們可以很多方法操作函數參數的默認值、未定義的參數和有定義的參數。在ES6中,我們可以用更簡單的語法實現這一切。
默認參數(Default Parameters)function addTwoNumbers(x, y) { x = x || 0; y = y || 0; return x + y; }
在ES6中,我們可以簡單的把默認值賦給參數:
function addTwoNumbers(x=0, y=0) { return x + y; } addTwoNumbers(2, 4); // 6 addTwoNumbers(2); // 2 addTwoNumbers(); // 0剩余參數(Rest Parameters)
在ES5中,我們這樣操作一個未定義的參數:
function logArguments() { for (var i=0; i < arguments.length; i++) { console.log(arguments[i]); } }
使用休止符(...)我們可以傳入大量未定義的參數:
function logArguments(...args) { for (let arg of args) { console.log(arg); } }已命名的參數(Named Parameters)
ES5中,一種處理已命名參數的方式是使用選項方式,這種方法來自jQuery。
function initializeCanvas(options) { var height = options.height || 600; var width = options.width || 400; var lineStroke = options.lineStroke || "black"; }
通過解構成正式參數的方式,我們可以實現同樣的功能:
function initializeCanvas( { height=600, width=400, lineStroke="black"}) { // Use variables height, width, lineStroke here }
如果我們想使全部參數值可選,我們可以用一個空對象這樣結構:
function initializeCanvas( { height=600, width=400, lineStroke="black"} = {}) { // ... }展開運算符(Spread Operator)
在ES5中,查找一個array中的最大值需要使用Math.max的apply方法:
Math.max.apply(null, [-1, 100, 9001, -32]); // 9001
在es6中,我們使用展開運算符將array傳遞給函數作為參數:
Math.max(...[-1, 100, 9001, -32]); // 9001
我們可以用這樣簡潔的語法鏈接數組字面量:
let cities = ["San Francisco", "Los Angeles"]; let places = ["Miami", ...cities, "Chicago"]; // ["Miami", "San Francisco", "Los Angeles", "Chicago"]類(classes)
在ES6之前,我們通過創建構造器函數,并且在其prototype上添加屬性的方法創建一個類:
function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } Person.prototype.incrementAge = function () { return this.age += 1; };
并且用一下方法創建繼承類:
function Personal(name, age, gender, occupation, hobby) { Person.call(this, name, age, gender); this.occupation = occupation; this.hobby = hobby; } Personal.prototype = Object.create(Person.prototype); Personal.prototype.constructor = Personal; Personal.prototype.incrementAge = function () { Person.prototype.incrementAge.call(this); this.age += 20; console.log(this.age); };
ES6 提供了十分有用的句法在后臺實現這一切,我們可以這樣直接創建一個類:
class Person { constructor(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } incrementAge() { this.age += 1; } }
并且用關鍵字extends實現繼承:
class Personal extends Person { constructor(name, age, gender, occupation, hobby) { super(name, age, gender); this.occupation = occupation; this.hobby = hobby; } incrementAge() { super.incrementAge(); this.age += 20; console.log(this.age); } }
建議:使用ES6的語法創建類模糊了后臺的實現和原型如何工作,這個好特性可以使我們的代碼更整潔。
SymbolsSymbol在ES6之前就已經出現了, 但是現在我們有了一個公共的接口可以直接使用。Symbol是唯一且不可改變的值,被用作哈希中的鍵。
Symbol()調用Symbol()或者Symbol(description)會創建一個不能在全局查找的獨一無二的符號。一種使用symbol()的情況是,利用自己的邏輯修補第三方的對象或命名空間,但不確定會不會在庫更新時產生沖突。例如,如果你想添加一個方法refreshCompontent到React.Component,并且確信這個方法他們不會在以后的更新中添加。
const refreshComponent = Symbol(); React.Component.prototype[refreshComponent] = () => { // do something } ###Symbol.for(key)
Symbol.for(key) 依然會創建一個唯一且不能修改的Symbol,但是它可以在全局被查找。兩次調用相同的Symbol.for(key) 會創建一樣的Symbol實例。注意,他和Symbol(description)不是相同的:
Symbol("foo") === Symbol("foo") // false Symbol.for("foo") === Symbol("foo") // false Symbol.for("foo") === Symbol.for("foo") // true
一個常見的symbol方法Symbol.for(key)是可互操作的。(使用這個方法)這個可以通過使用自己的代碼在包括已知接口的第三方的對象參數中查找symbol成員實現,例如:
function reader(obj) { const specialRead = Symbol.for("specialRead"); if (obj[specialRead]) { const reader = obj[specialRead](); // do something with reader } else { throw new TypeError("object cannot be read"); } }
在另一個庫中:
const specialRead = Symbol.for("specialRead"); class SomeReadableType { [specialRead]() { const reader = createSomeReaderFrom(this); return reader; } }
ES6中,一個值得注意的是關于Symbol的互操作性的例子是Symbol.iterator,它存在于Arrays、Strings、Generators等等的所有可迭代類型中。當作為一個方法調用的時候,它會返回一個具有迭代器接口的對象。
MapsMaps是JavaScript中十分有用的結構。在ES6之前, 我們通過對象創建哈希maps:
var map = new Object(); map[key1] = "value1"; map[key2] = "value2";
但是,這樣不能保護我們對已有屬性以外的重寫:
> getOwnProperty({ hasOwnProperty: "Hah, overwritten"}, "Pwned"); > TypeError: Property "hasOwnProperty" is not a function
Map允許我們使用set、get和search(等等)訪問屬性值。
let map = new Map(); > map.set("name", "david"); > map.get("name"); // david > map.has("name"); // true
最意想不到的是Map不再限制我們只使用字符串作為鍵,我們現在可以使用任何類型作為鍵而不會發生類型轉換。
let map = new Map([ ["name", "david"], [true, "false"], [1, "one"], [{}, "object"], [function () {}, "function"] ]); for (let key of map.keys()) { console.log(typeof key); // > string, boolean, number, object, function }
注意:當使用如map.get()等方法測試相等的時候,諸如function和object這樣的非原始值不能正常工作。因此,依然應該使用原始值(作為鍵),比如String、Boolean和Number。
我們也可以使用.entries()方法作為迭代器遍歷Map
for (let [key, value] of map.entries()) { console.log(key, value); }WeakMaps
ES6之前,為了保存私有數據,我們采取了很多方式。其中一個方法就是命名轉換:
class Person { constructor(age) { this._age = age; } _incrementAge() { this._age += 1; } }
但是命名轉換會引起代碼庫混亂,并且不能保證總是被支持。為此,我們使用WeakMaps存儲數據:
let _age = new WeakMap(); class Person { constructor(age) { _age.set(this, age); } incrementAge() { let age = _age.get(this) + 1; _age.set(this, age); if (age > 50) { console.log("Midlife crisis"); } } }
使用WeakMap存儲數據時的一個很有趣的事情是,這個key不會暴露出屬性名,需要使用Reflect.ownKeys()實現:
> const person = new Person(50); > person.incrementAge(); // "Midlife crisis" > Reflect.ownKeys(person); // []
使用WeakMap更實際的例子是在不污染DOM自身的情況下存儲與DOM元素相關的數據:
let map = new WeakMap(); let el = document.getElementById("someElement"); // 給元素存一個弱引用 map.set(el, "reference"); // 獲得元素的值 let value = map.get(el); // "reference" // 移除引用 el.parentNode.removeChild(el); el = null; // 元素被回收后,map是空的
如上所示,當一個對象被GC回收后,WeakMap會自動移除以其為標識符的鍵值對。
注意:為了進一步說明這個例子的實用性。當一個與DOM對應的對象的具有引用時,考慮jQuery如何存儲它。使用WeakMaps,jQuery可以在DOM元素被刪除時自動釋放與之關聯的內存。總而言之,對任何庫而言,WeakMaps對操作DOM元素是非常實用的。Promises
Promise允許我們把水平的代碼(回調函數的地獄):
func1(function (value1) { func2(value1, function (value2) { func3(value2, function (value3) { func4(value3, function (value4) { func5(value4, function (value5) { // Do something with value 5 }); }); }); }); });
轉換為豎直的代碼:
func1(value1) .then(func2) .then(func3) .then(func4) .then(func5, value5 => { // Do something with value 5 });
在ES6之前,我們使用bluebird或是Q,現在我們有了Promises:
new Promise((resolve, reject) => reject(new Error("Failed to fulfill Promise"))) .catch(reason => console.log(reason));
這里我們有2個handlers,resolve(Promise執行成功時調用的函數)和reject(Promise失敗時調用的函數)。
使用Promise的好處:使用嵌套的回調函數處理錯誤會很混亂。使用Promise,我們可以很清晰的使錯誤冒泡,并且就近處理它們。更好的是,在它處理成功(或失敗)之后Promise的值是不可修改的。
以下是個使用Promise的實例:
var request = require("request"); return new Promise((resolve, reject) => { request.get(url, (error, response, body) => { if (body) { resolve(JSON.parse(body)); } else { resolve({}); } }); });
我們可以使用Promise.all()并行的處理一個異步操作數組:
let urls = [ "/api/commits", "/api/issues/opened", "/api/issues/assigned", "/api/issues/completed", "/api/issues/comments", "/api/pullrequests" ]; let promises = urls.map((url) => { return new Promise((resolve, reject) => { $.ajax({ url: url }) .done((data) => { resolve(data); }); }); }); Promise.all(promises) .then((results) => { // Do something with results of all our promises });Generators
和Promise使我們避免回調函數的地獄相似,Generators可以扁平化我們的代碼——給我們一種同步執行異步代碼的感覺,Generators是個很重要的函數,它使我們可以暫停操作的執行,隨后返回表達式的值。
下面是使用Generator的一個簡單例子:
function* sillyGenerator() { yield 1; yield 2; yield 3; yield 4; } var generator = sillyGenerator(); > console.log(generator.next()); // { value: 1, done: false } > console.log(generator.next()); // { value: 2, done: false } > console.log(generator.next()); // { value: 3, done: false } > console.log(generator.next()); // { value: 4, done: false }
這里,next()使我們generator繼續推進,并且得到新的表達式的值(譯注:每次推進到下一個yield值)。當然,上面的例子很牽強,我們可以利用Generator以同步的方式寫異步代碼:
// 利用Generator屏蔽異步過程 function request(url) { getJSON(url, function(response) { generator.next(response); }); }
下面我們寫一個generator函數用來返回我們自己的數據:
function* getData() { var entry1 = yield request("http://some_api/item1"); var data1 = JSON.parse(entry1); var entry2 = yield request("http://some_api/item2"); var data2 = JSON.parse(entry2); }
利用yield的功能,我們確保entry1可以獲得返回的數據,用于解析并存儲到data1中。
當我們利用generator以同步的方式寫異步的代碼時,其中的錯誤不會簡單清晰的傳遞。因此,我們利用Promise加強generator:
function request(url) { return new Promise((resolve, reject) => { getJSON(url, resolve); }); }
我們寫了一個函數,利用next用來按序地一步步遍歷generator。該函數利用上述的請求方式并yeild一個Promise(對象)。
function iterateGenerator(gen) { var generator = gen(); (function iterate(val) { var ret = generator.next(); if(!ret.done) { ret.value.then(iterate); } })(); }
通過Promise加強generator后,我們可以利用Promise.catch和Promise.reject這樣清晰的方式傳播錯誤。只用這個加強版的Generator和以前一樣簡單:
iterateGenerator(function* getData() { var entry1 = yield request("http://some_api/item1"); var data1 = JSON.parse(entry1); var entry2 = yield request("http://some_api/item2"); var data2 = JSON.parse(entry2); });
我們可以重用寫好的代碼,像過去使用Generator一樣,這一點很強大。當我們利用generator以同步的方式寫異步的代碼的同時,利用一個不錯的方式保留了錯誤傳播的能力,我們實際上可以利用一個更為簡單的方式達到同樣的效果:異步等待(Async-Await)。
Async Await這是一個在ES2016(ES7)中即將有的特性,async await允許我們更簡單地使用Generator和Promise執行和已完成工作相同的任務:
var request = require("request"); function getJSON(url) { return new Promise(function(resolve, reject) { request(url, function(error, response, body) { resolve(body); }); }); } async function main() { var data = await getJSON(); console.log(data); // NOT undefined! } main();
在后臺,它的實現類似Generators。我(作者)強烈建議使用這個替代Generators + Promises。還會有很多的資源出現并使用ES7,同時,Babel也會用在這里。
Getter 和 Setter 函數ES6 已經支持了Getter和Setter函數,例如:
class Employee { constructor(name) { this._name = name; } get name() { if(this._name) { return "Mr. " + this._name.toUpperCase(); } else { return undefined; } } set name(newName) { if (newName == this._name) { console.log("I already have this name."); } else if (newName) { this._name = newName; } else { return false; } } } var emp = new Employee("James Bond"); // 內部使用了get方法 if (emp.name) { console.log(emp.name); // Mr. JAMES BOND } // 內部使用了setter(譯注:原文中這一句和上一句注釋的表述就這么不一樣) emp.name = "Bond 007"; console.log(emp.name); // Mr. BOND 007
最新的瀏覽器都支持對象中的getter/setter函數,我們可以使用他們計算屬性、添加事件以及在setting和getting前的預處理
var person = { firstName: "James", lastName: "Bond", get fullName() { console.log("Getting FullName"); return this.firstName + " " + this.lastName; }, set fullName (name) { console.log("Setting FullName"); var words = name.toString().split(" "); this.firstName = words[0] || ""; this.lastName = words[1] || ""; } } person.fullName; // James Bond person.fullName = "Bond 007"; person.fullName; // Bond 007
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97358.html
摘要:認識模塊作為一名編程語言,一直以來沒有模塊的概念。在之前,有主要的個模塊化方案和。這樣引入模塊和引入模塊方法差不多,其代表是。關鍵字用于規定模塊的對外接口,關鍵字用于輸入其他模塊提供的功能。 認識模塊 JS 作為一名編程語言,一直以來沒有模塊的概念。嚴重導致大型項目開發受阻,js 文件越寫越大,不方便維護。其他語言都有模塊的接口,比如 Ruby 的 require,python 的 i...
摘要:數組的擴展將類數組對象和可遍歷對象轉化為真正的數組。這兩個函數的參數都是回調函數。遍歷數組找到符合條件回調函數返回為的第一個值返回其值返回其下標。這三個方法用來遍歷數組返回一個遍歷器供使用其中是對鍵的遍歷是對值的遍歷是對鍵值對的遍歷。 數組的擴展 Array, from() 將類數組對象和可遍歷對象轉化為真正的數組。 var arrayLike = { 0 : a, 1 : b...
摘要:和塊級作用域塊級作用于對于強類型語言經驗的人應該非常好理解一言以蔽之對變量作用于分隔使用了函數詞法作用域而使用花括號塊作用域。但是花括號對于沒有聲明直接定義以及用聲明的變量沒有影響這些變量依然遵守詞法作用域規則。 let、const和塊級作用域 塊級作用于對于強類型語言經驗的人應該非常好理解, 一言以蔽之:ES5對變量作用于分隔使用了函數(詞法作用域), 而ES6使用花括號(塊作用域)...
摘要:理解元編程和是屬于元編程范疇的,能介入的對象底層操作進行的過程中,并加以影響。元編程中的元的概念可以理解為程序本身。中,便是兩個可以用來進行元編程的特性。在之后,標準引入了,從而提供比較完善的元編程能力。 導讀 幾年前 ES6 剛出來的時候接觸過 元編程(Metaprogramming)的概念,不過當時還沒有深究。今天在應用和學習中不斷接觸到這概念,比如 mobx 5 中就用到了 Pr...
閱讀 3512·2021-11-15 11:38
閱讀 825·2021-11-08 13:27
閱讀 2235·2021-07-29 14:50
閱讀 2970·2019-08-29 13:06
閱讀 838·2019-08-29 11:22
閱讀 2408·2019-08-29 11:04
閱讀 3499·2019-08-28 18:23
閱讀 890·2019-08-26 13:46