Little H title

this is subtitle


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 公益404

javascript-数组

发表于 2019-05-12 | 分类于 javascript教程

javascript-数组

随着时间的推移,JavaScript 数组有了越来越多的功能,有时候想知道何时应该使用何种方法是个很棘手的问题。本节旨在解释您应该在什么场景下使用什么方法,截至 2018 年。

初始化数组

JavaScript 代码:

1
2
3
4
const a = [];
const a = [1, 2, 3];
const a = Array.of(1, 2, 3);
const a = Array(6).fill(1); // 初始化一个包含6项元素的数组,每项使用 1 填充,即:[1, 1, 1, 1, 1, 1]

Array.of()

Array.of() 方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。

Array.of() 和 Array 构造函数之间的区别在于处理整数参数:Array.of(7) 创建一个具有单个元素 7 的数组,而 Array(7) 创建一个长度为 7的空数组(注意:这是指一个有 7 个空位(empty)的数组,而不是由 7 个 undefined 组成的数组)。

1
2
3
4
5
Array.of(7); // [7]
Array.of(1, 2, 3); // [1, 2, 3]

Array(7); // [ , , , , , , ] 空位而不是 undefined
Array(1, 2, 3); // [1, 2, 3]

Array.from() 方法从一个类似数组或可迭代对象中创建一个新的数组实例。

1
2
Array.from('foo'); // ["f", "o", "o"]
Array.of('foo'); // ['foo']

不要使用旧语法(只是将它用于类型化数组)

JavaScript 代码:

1
2
const a = new Array(); //never use
const a = new Array(1, 2, 3); //never use

获取数组长度

JavaScript 代码:

1
const l = a.length;

使用 every 迭代数组

every() 方法测试数组的所有元素是否都通过了指定函数的测试。

JavaScript 代码:

1
a.every(f);

迭代 a 直到 f() 返回 false。

使用 some 迭代数组

some() 方法测试数组中的某些元素是否通过由指定函数的真值测试。

JavaScript 代码:

1
a.some(f);

迭代 a 直到 f() 返回 true。

遍历数组并返回函数结果组成的新数组 map

JavaScript 代码:

1
const b = a.map(f);

遍历 a,返回每一个 a 元素执行 f() 产生的结果数组。

过滤数组 filter

JavaScript 代码:

1
const b = a.filter(f);

遍历 a,返回每一个 a 元素执行 f() 都为 true 的新数组。

Reduce

JavaScript 代码:

1
2
3
a.reduce((accumulator, currentValue, currentIndex, array) => {
//...
}, initialValue);

reduce() 对数组中每一项都调用回调函数,并逐步计算计算结果。如果 initaiValue 存在,accumulator 在第一次迭代时等于这个值。

示例:

JavaScript 代码:

1
2
3
4
5
6
7
8
9
10
[1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => {
return accumulator * currentValue;
}, 1);

// iteration 1: 1 * 1 => return 1
// iteration 2: 1 * 2 => return 2
// iteration 3: 2 * 3 => return 6
// iteration 4: 6 * 4 => return 24

// return value is 24

forEach

ES6 新增。

JavaScript 代码:

1
a.forEach(f);

遍历 a 执行 f,中途不能停止。 return break continue 都不行

示例:

JavaScript 代码:

1
2
3
a.forEach((v) => {
console.log(v);
});

for..of

ES6 新增。

JavaScript 代码:

1
2
3
for (let v of a) {
console.log(v);
}

for

JavaScript 代码:

1
2
3
for (let i = 0; i < a.length; i += 1) {
//a[i]
}

遍历 a,可以通过 return 或者 break 中止循环,通过 continue 跳出循环。

@@iterator

ES6 新增。

获取数组迭代器的值:

JavaScript 代码:

1
2
3
4
5
6
const a = [1, 2, 3];
let it = a[Symbol.iterator]();

console.log(it.next().value); //1
console.log(it.next().value); //2
console.log(it.next().value); //3

.entries() 返回一个键值对的迭代器:

JavaScript 代码:

1
2
3
4
5
let it = a.entries();

console.log(it.next().value); //[0, 1]
console.log(it.next().value); //[1, 2]
console.log(it.next().value); //[2, 3]

.keys() 返回包含所有键名的迭代器:

JavaScript 代码:

1
2
3
4
let it = a.keys();
console.log(it.next().value); //0
console.log(it.next().value); //1
console.log(it.next().value); //2

数组结束时 .next() 返回 undefined。你可以通过 it.next() 返回的 value, done 值检测迭代是否结束。当迭代到最后一个元素时 done 的值始终为 true 。

在数组末尾追加值

JavaScript 代码:

1
a.push(4);

在数组开头添加值

JavaScript 代码:

1
2
a.unshift(0);
a.unshift(-2, -1);

移除数组中的值

删除末尾的值

JavaScript 代码:

1
a.pop();

删除开头的值

JavaScript 代码:

1
a.shift();

删除任意位置的值

JavaScript 代码:

1
2
a.splice(0, 2); // 删除前2项元素
a.splice(3, 2); // 删除从索引 3 开始的 2 个元素

不要使用 remove() ,因为它会留下未定义的值。

移除并插入值

JavaScript 代码:

1
2
3
a.splice(2, 3, 2, 'a', 'b');
// 删除从索引 2 开始的 3 个元素,
// 并且从索引 2 开始添加 2 元素('a', 'b')

合并多个数组

JavaScript 代码:

1
2
3
const a = [1, 2];
const b = [3, 4];
a.concat(b); // 1, 2, 3, 4

查找数组中特定元素

ES5 写法:
JavaScript 代码:

1
a.indexOf();

返回匹配到的第一个元素的索引,元素不存在返回 -1。

JavaScript 代码:

1
a.lastIndexOf();

返回匹配到的最后一个元素的索引,元素不存在返回 -1。

ES6 写法:
JavaScript 代码:

1
2
3
a.find((element, index, array) => {
//return true or false
});

返回符合条件的第一个元素,如果不存在返回 undefined。

通常这么用:

JavaScript 代码:

1
a.find((x) => x.id === my_id);

上面的例子会返回数组中 id === my_id 的第一个元素。

findIndex 返回符合条件的第一个元素的索引,如果不存在返回 undefined:

JavaScript 代码:

1
2
3
a.findIndex((element, index, array) => {
//return true or false
});

ES7 写法:
JavaScript 代码:

1
a.includes(value);

如果 a 包含 value 返回 true。

JavaScript 代码:

1
a.includes(value, i);

如果 a 从位置 i 后包含 value 返回 true。

数组排序

按字母顺序排序(按照 ASCII 值 – 0-9A-Za-z):

JavaScript 代码:

1
2
3
4
5
const a = [1, 2, 3, 10, 11];
a.sort(); //1, 10, 11, 2, 3

const b = [1, 'a', 'Z', 3, 2, 11];
b = a.sort(); //1, 11, 2, 3, Z, a

自定义排序

JavaScript 代码:

1
2
const a = [1, 10, 3, 2, 11];
a.sort((a, b) => a - b); //1, 2, 3, 10, 11

逆序

JavaScript 代码:

1
a.reverse();

数组转字符串

JavaScript 代码:

1
a.toString();

返回字符串类型的值

JavaScript 代码:

1
a.join();

返回数组元素拼接的字符串。传递参数以自定义分隔符:

JavaScript 代码:

1
a.join(',');

复制所有值

JavaScript 代码:

1
2
const b = Array.from(a);
const b = Array.of(...a);

复制部分值

JavaScript 代码:

1
const b = Array.from(a, (x) => x % 2 == 0);

将值复制到本身其它位置

Array​.prototype​.copy​Within()

JavaScript 代码:

1
2
3
4
5
6
7
8
9
const a = [1, 2, 3, 4];
a.copyWithin(0, 2); // [3, 4, 3, 4]
const b = [1, 2, 3, 4, 5];
b.copyWithin(0, 2); // [3, 4, 5, 4, 5]
// 0 是拷贝的值插到哪里
// 2 从哪里开始拷贝
const c = [1, 2, 3, 4, 5];
c.copyWithin(0, 2, 4); // [3, 4, 3, 4, 5]
//4 是结束索引

拷贝几个元素,就从插入位置开始替换几个元素。

参考

JavaScript 数组(Arrays),一份权威的备忘清单 – JavaScript 完全手册(2018 版)

javascript-箭头函数适用与不适用场景

发表于 2019-05-12 | 分类于 javascript教程

javascript-箭头函数适用与不适用场景

现代 JavaScript 中最引人注目的功能之一是引入了箭头函数,用 => 来标识。

这种函数有两大优点 – 非常简洁的语法,和更直观的作用域和 this 的绑定。

这些优点有时导致箭头函数比其他形式的函数声明更受欢迎。

例如,受欢迎的 airbnb eslint 配置 会在您创建匿名函数时强制使用 JavaScript 箭头函数。

然而,就像工程中的任何东西一样,箭头函数优点很明显,同时也带来了一些负面的东西。 使用他们的时候需要权衡一下。

学习如何权衡是更好地使用箭头函数的关键。

在本文中,我们将首先回顾箭头函数的工作原理,然后深入研究箭头函数改进代码的示例,最后深入研究箭不建议使用头函数的示例。

JavaScript 箭头函数究竟是什么?

JavaScript 箭头函数大致相当于 python 中的 lambda 函数 或 Ruby 中的 blocks。

这些是匿名函数,它们有自己的特殊语法,接受一定数量的参数,并在其封闭的作用域的上下文(即定义它们的函数或其他代码)中操作。

让我们依次分解这些部分。

箭头函数语法

箭头函数具有单一的总体结构,然后在特殊情况下可以通过多种方式简化它们。 核心结构如下所示:

JavaScript 代码:

1
2
3
(argument1, argument2, ...argumentN) => {
// function body
};

括号内的是参数列表,后跟“胖箭头”(=>),最后是函数体。

这与传统函数非常相似,我们只是省略 function 关键字并在参数后添加一个胖箭头(=>)。

然而,有许多方法可以简化箭头函数。

首先,如果函数体是单个表达式,则可以不使用花括号并将其置于内联(就是一行 inline)中(省略大括号直接将表达式写在一行中)。 表达式的结果将由函数返回。 例如:

JavaScript 代码:

1
const add = (a, b) => a + b;

其次,如果只有一个参数,你甚至可以省略参数的括号。例如:

JavaScript 代码:

1
const getFirst = (array) => array[0];

正如您所看到的,这是一些非常简洁的语法,我们将重点介绍后面的好处。

高级语法

有一些高级语法可以了解一下。

首先,如果您尝试使用内联单行表达式语法,但您返回的值是对象字面量。您可能会认为这看起来应该是这样的:

JavaScript 代码:

1
(name, description) => {name: name, description: description};

问题是这种语法比较含糊不清,容易引起歧义 : 看起来好像你正试图创建一个传统的函数体。 如果你碰巧想要一个对象的单个表达式,请用括号包裹该对象:

JavaScript 代码:

1
(name, description) => ({ name: name, description: description });

或者就是直接用 return

1
2
3
(name, description) => {
return { name: name, description: description };
};

封闭的上下文作用域

与其他形式的函数不同,箭头函数没有自己的 执行期上下文。

实际上,这意味着 this 和 arguments 都是从它们的父函数继承而来的。

例如,使用和不使用箭头函数比较以下代码:

JavaScript 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const test = {
name: 'test object',
createAnonFunction: function() {
return function() {
console.log(this.name);
console.log(arguments);
};
},

createArrowFunction: function() {
return () => {
console.log(this.name);
console.log(arguments);
};
}
};

我们有一个简单的 test 对象,有两个方法 – 每个方法都返回一个匿名函数。

不同之处在于第一个方法使用传统函数表达式,而后者中使用箭头函数。

如果我们使用相同参数,在控制台中运行它们,我们会得到完全不一样的结果。

JavaScript 代码:

1
2
3
4
5
6
7
8
9
10
> const anon = test.createAnonFunction('hello', 'world');
> const arrow = test.createArrowFunction('hello', 'world');

> anon();
undefined
{}

> arrow();
test object
{ '0': 'hello', '1': 'world' }

第一个匿名函数有自己的函数上下文,因此当您调用它时,test 对象的 this.name 没有可用的引用,也没有创建它时调用的参数。 相当于是 window 在调用, 但window 没有 name

另一个,箭头函数具有与创建它的函数完全相同的函数上下文,使其可以访问 argumetns 和 test 对象。

使用箭头函数改进您的代码

传统 lambda 函数的主要用例之一,就是用于遍历列表中的项,现在用 JavaScript 箭头函数实现。

比如你有一个有值的数组,你想去 map 遍历每一项,这时使用箭头函数非常理想:

JavaScript 代码:

1
2
const words = ['hello', 'WORLD', 'Whatever'];
const downcasedWords = words.map((word) => word.toLowerCase());

一个非常常见的例子是提取对象中的某个特定值:

JavaScript 代码:

1
const names = objects.map((object) => object.name);

类似地,当用现代迭代样式取代传统的 for 循环,一般我们使用 forEach 循环,箭头函数能够保持 this 来自于父级,让他们非常直观

类似的,当用 forEach 来替换传统 for 循环的时候,实际上箭头函数会直观的保持 this 来自于父一级

JavaScript 代码:

1
2
3
this.examples.forEach((example) => {
this.runExample(example);
});

Promises 和 Promise 链

箭头函数的另一个可以使代码更清晰,更直观的地方是管理异步代码。

Promises 使得管理异步代码变得容易很多(即使你很欢快地使用 async / await,你仍然应该理解 async / await 是建立在 Promises 之上的 !)

Promise 还涉及包装 Ajax 和 fetch

JavaScript 回调函数怎么理解
回调函数有高阶函数的味道, 不过是返回这个传入的函数执行结果

常见的一种模式吧

1
2
3
4
5
6
7
8
var func1 = function(callback) {
//do something.
callback && typeof callback === 'function' && callback();
};

var fn = function(callback) {
callback('callback');
};

但是,虽然使用 promises 仍然需要定义在异步代码或调用完成后运行的函数。

这是箭头函数的理想位置,特别是如果您生成的函数是有状态的,同时想引用对象中的某些内容。 例如:

JavaScript 代码:

1
2
3
this.doSomethingAsync().then((result) => {
this.storeResult(result);
});

对象转换

箭头函数的另一个常见且极其强大的用途是封装对象转换。

例如,在 Vue.js 中,有一种通用模式,用于使用 mapState 将 Vuex 存储的各个部分直接包含到 Vue 组件中。

这涉及定义一组 “mappers” ,这些 “mappers” 将从原始的完整的 state 对象转换为提取所涉及组件所需的内容。

这些简单的转换使用箭头函数再合适不过了。比如:

JavaScript 代码:

1
2
3
4
5
6
7
8
export default {
computed: {
...mapState({
results: state => state.results,
users: state => state.users,
});
}
}

你不应该使用箭头函数的情景

在许多情况下,使用箭头函数不是一个好主意。 他们不仅不会帮助你,而且会给你带来一些不必要的麻烦。

第一个是对象的方法。 这是一个函数上下文的例子,这对于我们理解很有帮助。

React 中常用了

有一段时间使用 Class(类)属性语法和箭头函数的组合,作为创建“自动绑定方法”的方式,例如, 事件处理程序可以使用,但仍然绑定到类的方法。

这看起来像是这样的:

JavaScript 代码:

1
2
3
4
5
6
7
class Counter {
counter = 0;

handleClick = () => {
this.counter++;
};
}

这样,即使 handleClick 由事件处理程序调用,而不是在 Counter 实例的上下文中调用,它仍然可以访问实例的数据。

这种方法的缺点很多,在本文中很好地记录。

虽然使用这种方法确实为您提供了具有绑定函数的快捷方式,但该函数以多种不直观的方式运行,如果您尝试将此对象作为原型进行子类化/使用,则会不利于测试,同时也会产生很多问题。

相反,使用常规函数,如果需要,将其绑定到构造函数中的实例:

JavaScript 代码:

1
2
3
4
5
6
7
8
9
10
11
class Counter {
counter = 0;

handleClick() {
this.counter++;
}

constructor() {
this.handleClick = this.handleClick.bind(this);
}
}

深层的调用链

箭头函数可能让你遇到麻烦的另一个地方是,它们被用于许多不同的组合,特别是在函数深层调用链中。

核心原因与匿名函数相同 – 它们给出了非常糟糕的堆栈跟踪。

如果你的函数只是向下一级,比如在迭代器里面,那也不是太糟糕,但是如果你把所有的函数定义为箭头函数,并在它们之间来回调用,你就会陷入困境 遇到一个错误的时候,只是收到错误消息,如:

JavaScript 代码:

1
2
3
4
5
{anonymous}()
{anonymous}()
{anonymous}()
{anonymous}()
{anonymous}()

有动态上下文的函数

箭头函数可能让您遇到麻烦的最后一种情况就是吗, this 是动态绑定的时候。

如果您在这些位置使用箭头函数,那么动态绑定将不起作用,并且你(或稍后使用你的代码的其他人)可能会对事情未按预期执行的原因感到困惑。

一些典型的例子:

  • 事件处理程序是通过将 this 设置为事件的 currentTarget 属性来调用。
  • 如果您仍在使用 jQuery ,则大多数 jQuery 方法将 this 设置为已选择的 dom 元素。
  • 如果您正在使用 Vue.js ,则方法和计算函数通常将 this 设置为 Vue 组件。

当然你可以故意使用箭头函数来覆盖这种行为,但特别是在 jQuery 和 Vue 的情况下,这通常会干扰正常运行,让你感到困惑的是为什么看起来与附近其他代码相同的代码不起作用。

总结

箭头函数是 JavaScript 语言的一个非常有必要的补充,并且在许多情况下使代码更符合人们的阅读习惯。

然而,像所有其他特性一样,它们有优点和缺点。 我们应该将它们作为我们工具箱中的另一个工具,而不是作为所有函数的全面替代品。

英文原文:https://zendev.com/2018/10/01/javascript-arrow-functions-how-why-when.html

参考

JavaScript 箭头函数:适用与不适用场景

javascript-箭头函数

发表于 2019-05-12 | 分类于 javascript教程

javascript-箭头函数

箭头函数(Arrow Function)是 ES6 / ES2015 中最具影响力的变化之一,现在它们被广泛使用。 它们与常规函数略有不同。 我们来看看下面的一些情况。

我前面已经介绍了箭头函数,但它们非常重要,它们需要再重点介绍一下。

在 ES6/ECMAScript2015 中引入了箭头函数,自从它们引入后,它们彻底改变了 JavaScript 代码写法(和工作方式)。

在我看来,这种变化非常受欢迎,你现在很少在现代代码库中看到 function 关键字。

在视觉上,这是一个简单而受欢迎的变化,您使用更短的语法编写函数,从:

JavaScript 代码:

1
2
3
const myFunction = function foo() {
//...
};

变成:

JavaScript 代码:

1
2
3
const myFunction = () => {
//...
};

如果函数体只包含一条语句,则可以省略花括号,并在一行上写全部:

JavaScript 代码:

1
const myFunction = () => doSomething();

参数在括号中传递:

JavaScript 代码:

1
const myFunction = (param1, param2) => doSomething(param1, param2);

如果你只有一个参数,你甚至可以省略括号:

JavaScript 代码:

1
const myFunction = (param) => doSomething(param);

由于这种简短的语法,箭头函数 鼓励使用短函数 。

隐式返回

箭头函数允许您具有隐式返回,即返回值不必使用 return 关键字。

它在函数体内只有一个语句时有效:

JavaScript 代码:

1
2
3
const myFunction = () => 'test';

myFunction(); //'test'

箭头函数中 this 如何工作

this 概念可能很难理解,因为它根据上下文的不同而变化,同事也受到 JavaScript 的模式(严格模式 strict mode 或非严格模式)的影响。

理清这个概念很重要,因为与常规函数相比,this 在箭头函数的表现有很大的不同。

当定义为对象的方法时,在常规函数中,this 指的是对象,因此您可以:

JavaScript 代码:

1
2
3
4
5
6
7
const car = {
model: 'Fiesta',
manufacturer: 'Ford',
fullName: function() {
return `${this.manufacturer} ${this.model}`;
}
};

调用 car.fullName() 将返回 “Ford Fiesta” 。

箭头函数的 this 作用域继承自执行期上下文。 箭头函数根本不绑定 this ,因此它的值将在调用栈中查找,因此在此代码中 car.fullName() 将不起作用,并将返回字符串 "undefined undefined":

JavaScript 代码:

1
2
3
4
5
6
7
const car = {
model: 'Fiesta',
manufacturer: 'Ford',
fullName: () => {
return `${this.manufacturer} ${this.model}`;
}
};

因此,箭头函数不适合作为对象方法使用。

在实例化对象时,箭头函数也不能用作构造函数。 它会引发 TypeError 。

当不需要动态上下文时,我们应该使用常规函数。

处理事件时也有类似的问题。 DOM 事件侦听器将 this 设置为目标元素,如果您在事件处理程序中依赖于 this ,则需要常规函数:

JavaScript 代码:

1
2
3
4
5
6
7
8
9
const link = document.querySelector('#link');
link.addEventListener('click', () => {
// this === window
});

const link = document.querySelector('#link');
link.addEventListener('click', function() {
// this === link
});

了解更多关于箭头函数的知识请查看 JavaScript 箭头函数:适用与不适用场景

参考

JavaScript 箭头函数(Arrow Function) – JavaScript 完全手册(2018 版)

javascript-function函数

发表于 2019-05-12 | 分类于 javascript教程

javascript-function 函数

现在我们将学习 JavaScript 中所有关于函数的知识,从概述到小细节帮助你更好的使用 JavaScript 函数。注:本文没有一句废话,对于新手,可以作为该知识点的入门指南,对于有经验的开发人员可以作为一次很好的回顾。

JavaScript 中的所有内容都在函数中执行。

函数是一个自包含的代码块,可以定义一次,并运行任意次数。

函数可以选择接受参数,并返回一个值。

JavaScript 中的函数是 对象,一种特殊的对象:function objects(函数对象)。

另外,函数在 JavaScript 中是一等公民,因为它们可以被赋值给一个值,它们可以作为参数传递并用作返回值。

高阶函数, 能当参数又能作为返回值返回

让我们从“旧的”,ES6 / ES2015 之前的语法开始。 这是一个函数声明:

JavaScript 代码:

1
2
3
function dosomething(foo) {
// do something
}

在 ES6 / ES2015 流行的当下,简称为常规函数。

可以将函数分配给变量(这称为函数表达式):

JavaScript 代码:

1
2
3
4
const dosomething = function(foo) {
// 匿名函数
// do something
};

命名函数表达式类似,但在堆栈调用跟踪中更好用,这在发生错误时很有用 – 它保存函数的名称:

JavaScript 代码:

1
2
3
const dosomething = function dosomething(foo) {
// do something
};

ES6 / ES2015 引入了箭头函数,在使用内联函数时,它们特别适合用作参数或回调函数:

JavaScript 代码:

1
2
3
const dosomething = (foo) => {
//do something
};

箭头函数与上面的其他函数定义有很大的不同,我们会在后面的章节中详细介绍。

参数

一个函数可以有一个或多个参数。

JavaScript 代码:

1
2
3
4
5
6
7
8
9
const dosomething = () => {
//do something
};
const dosomethingElse = (foo) => {
//do something
};
const dosomethingElseAgain = (foo, bar) => {
//do something
};

从 ES6 / ES2015 开始,函数可以具有参数的默认值:

JavaScript 代码:

1
2
3
const dosomething = (foo = 1, bar = 'hey') => {
//do something
};

这允许您在不填充所有参数的情况下调用函数:

JavaScript 代码:

1
2
dosomething(3);
dosomething();

ES2017 引入了参数的尾随逗号,这个功能有助于减少因移动参数时丢失逗号而导致的错误(例如,移动中间的最后一个):

JavaScript 代码:

1
2
3
4
const dosomething = (foo = 1, bar = 'hey',) => {
//do something
};
dosomething(2, 'ho!',);

您可以将所有参数包装在一个数组中,并在调用函数时使用展开运算符:

JavaScript 代码:

1
2
3
4
5
const dosomething = (foo = 1, bar = 'hey') => {
//do something
};
const args = [2, 'ho!'];
dosomething(...args);

使用许多参数,记住顺序可能很困难。这时使用对象解构这个对象允许保留参数名称:

与解构赋值默认值结合使用, 即设置函数参数默认值和解构赋值一起用

JavaScript 代码:

1
2
3
4
5
6
7
8
const dosomething = ({ foo = 1, bar = 'hey' }) => {
// 等号 是默认值, 传进来一个对象
//do something
console.log(foo); // 2
console.log(bar); // 'ho!'
};
const args = { foo: 2, bar: 'ho!' }; // 对象
dosomething(args);

注意这里 函数参数是个对象 用的是 { foo = 1, bar = 'hey' },用的是等号 ,不是 { foo : 1, bar : 'hey' }。

这里 上面代码只使用了对象的解构赋值默认值,没有使用函数参数的默认值。

对象解构
参数默认值可以与解构赋值的默认值,结合起来使用。

1
2
3
4
5
6
7
8
function foo({ x, y = 5 }) {
console.log(x, y);
}

foo({}); // undefined 5
foo({ x: 1 }); // 1 5
foo({ x: 1, y: 2 }); // 1 2
foo(); // TypeError: Cannot read property 'x' of undefined

上面代码只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数 foo 的参数是一个对象时,变量 x 和 y 才会通过解构赋值生成。如果函数 foo 调用时没提供参数,变量 x 和 y 就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。

函数参数如果定义时为对象, 就会使用对象的解构赋值默认值,, 函数的参数的默认值用看下面
其实也一样理解, 如果传入的参数用了 = 号就是设置参数默认值, 如果传入的是 {}对象, 那么先是解构赋值

1
2
3
4
5
function foo({ x, y = 5 } = {}) {
console.log(x, y);
}

foo(); // undefined 5

上面代码指定,如果没有提供参数,函数 foo 的参数默认为一个空对象。

作为练习,请问下面两种写法有什么差别?

1
2
3
4
5
6
7
8
9
// 写法一
function m1({ x = 0, y = 0 } = {}) {
return [x, y];
}

// 写法二
function m2({ x, y } = { x: 0, y: 0 }) {
return [x, y];
}

上面两种写法都对函数的参数设定了默认值,
区别是写法一函数参数的默认值是空对象,但是设置了对象解构赋值的默认值;
写法二函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 函数没有参数的情况
m1(); // [0, 0]
m2(); // [0, 0]

// x 和 y 都有值的情况
m1({ x: 3, y: 8 }); // [3, 8]
m2({ x: 3, y: 8 }); // [3, 8]

// x 有值,y 无值的情况
m1({ x: 3 }); // [3, 0]
m2({ x: 3 }); // [3, undefined]

// x 和 y 都无值的情况
m1({}); // [0, 0];
m2({}); // [undefined, undefined]

m1({ z: 3 }); // [0, 0]
m2({ z: 3 }); // [undefined, undefined]

返回值

每个函数都返回一个值,默认情况下是 undefined 。

return.png

任何函数在其代码行结束时或执行流找到 return 关键字时终止执行。

当 JavaScript 遇到 return 关键字时,它退出函数执行并将控制权交还给其调用者。

如果 return 后面跟一个值,则该值将作为函数的结果返回:

JavaScript 代码:

1
2
3
4
const dosomething = () => {
return 'test';
};
const result = dosomething(); // result === 'test'

您只能返回一个值。

要模拟返回多个值,可以返回对象字面量或数组,并在调用函数时使用解构赋值。

使用数组:

1
2
3
4
5
6
7
const dosomething = () => {
return ['hello', 6];
};

const [name, age] = dosomething();
name; // hello
age; // 6

使用对象:

1
2
3
4
5
6
7
const dosomething = () => {
return { name: 'hello', age: 6 };
};

const { name, age } = dosomething();
name; // hello
age; // 6

嵌套函数

可以在函数中定义其他函数:

JavaScript 代码:

1
2
3
4
5
const dosomething = () => {
const dosomethingelse = () => {};
dosomethingelse();
return 'test';
};

被嵌套函数的作用域是嵌套它的函数,不能从外部调用。

对象方法

当用作对象属性时,函数称为方法:

JavaScript 代码:

1
2
3
4
5
6
7
8
9
const car = {
brand: 'Ford',
model: 'Fiesta',
start: function() {
// 对象方法
console.log(`Started`);
}
};
car.start();

箭头函数中的“this”

当箭头函数与常规函数用作对象方法时,有一个重要的行为区别。考虑这个例子:

JavaScript 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const car = {
brand: 'Ford',
model: 'Fiesta',
start: function() {
console.log(`Started ${this.brand} ${this.model}`)
},
stop: () => {
console.log(`Stopped ${this.brand} ${this.model}`)
}
}

car.start()
Started Ford Fiesta
car.stop()
Stopped undefined undefined

stop() 方法无法正常工作。

没有 this 也没有 prototype, 但都有 constructor是Function()

这是因为在两个函数声明风格中处理 this 的方式是不同的。this 在箭头函数中指的是封闭的函数上下文,在这种例子中是 window 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const car = {
brand: 'Ford',
model: 'Fiesta',
start: function() {
console.log(this)
console.log(`Started ${this.brand} ${this.model}`)
},
stop: () => {
console.log(this)
console.log(`Stopped ${this.brand} ${this.model}`)
}
}

car.start()
{brand: "Ford", model: "Fiesta", start: ƒ, stop: ƒ}
Started Ford Fiesta
car.stop()
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
Stopped undefined undefined

this,是指使用 function() 的宿主对象

这意味着箭头函数不适合用于对象方法和构造函数(箭头函数构造函数实际上会在调用时引发 TypeError )。

IIFE,立即调用函数表达式

IIFE 是一个在声明后立即执行的函数:

JavaScript 代码:

1
2
3
(function dosomething() {
console.log('executed');
})();

您可以将结果分配给变量:

JavaScript 代码:

1
2
3
const something = (function dosomething() {
return 'something';
})();

它们非常方便,因为您无需在定义后单独调用该函数。

函数提升(Hoisting)

执行代码之前的 JavaScript 会根据某些规则对其进行重新排序。

特别需要记住的一点是函数会被移动到其作用域的顶部。所以下面的写法是合法的:

JavaScript 代码:

1
2
3
4
dosomething();
function dosomething() {
console.log('did something');
}

JavaScript 中的 Hoisting (变量提升和函数声明提升) 666

在内部,JavaScript 在调用之前移动函数,以及在同一作用域内找到的所有其他函数:

JavaScript 代码:

1
2
3
4
function dosomething() {
console.log('did something');
}
dosomething();

看下面的代码,如果您使用命名函数表达式,因为您正在使用 javascript-变量 ,所以会发生不同的事情。我们说的变量提升,其实是变量声明被提升,但不是值被提升,因此下面的代码中不是那个函数被提升。

JavaScript 代码:

1
2
3
4
dosomething();
const dosomething = function dosomething() {
console.log('did something');
};

上面代码不会工作

function.png

上面代码内部发生的事情是这样的.

JavaScript 代码:

1
2
3
4
5
const dosomething
dosomething()
dosomething = function dosomething() {
console.log('did something')
}

let 声明也是如此。 var 声明也不起作用,但有不同的错误:

let.png

这是因为 var 声明被提升并初始化为 undefined 作为值,而 const 和 let 被提升但未初始化。

const 和 let 要先声明后使用的

1
2
3
4
5
ss

let ss = 3
Uncaught ReferenceError: Cannot access 'ss' before initialization
at <anonymous>:1:1

注:

关于 const 和 let 是否被 hoisting(提升)的问题争论了好几年了至今没有定论,主要原因是 hoisting(提升)和 TDZ (temporal dead zone,暂时性死区)都是非官方的解释,只是帮助大家理解 JavaScript 机制的说法。

本文最后说到的示例有两种解释:

第一种解释: const 和 let 声明的变量是不会提升,因为没有提升,所以报 not defined 错误,var 提升了,初始值为 undefined ,但是被当做了函数运算,undefined 不是函数,所以说变量不是函数。

第二种解释:JavaScript 中所有的声明 (var, let, const, function, function*, class) 都会被 hoisting(提升)。var / function / function*声明和 let / const / class 声明之间的区别是初始化。const 和 let 有一个TDZ,初始化被推迟,也就是变量保持未初始化。 所以访问它时会引发 ReferenceError 异常。 只有在碰到 let / const / class语句时,才会初始化变量,这叫临时死区。和变量提升是两码事

暂时性死区就是要你先声明后使用

ES6 中 let 暂时性死区详解
let MDN
暂时性死区(TDZ)并不神秘

1
2
console.log(x); // throws a ReferenceError
let x = 'hey';

老的 var 和新的 let/const 声明(除了他们的作用域外)最大的主要不同点之一就是后者被暂时性死区所约束,也就是当他们在初始化之前被访问(读/写)的时候将抛出 ReferenceError , 而不是跟 var 声明变量一样返回 undefined 。

  • let hoisting?
  • Temporal dead zone
  • 我用了两个月的时间才理解 let

反正就是很乱!写文档的说文档不正确,MDN的文档也是一会这个解释,一会那个解释。大家觉得哪个解释适合自己理解,就用哪个解释吧!

参考

你应该知道的 JavaScript function(函数) – JavaScript 完全手册(2018 版)

javascript-字面量模板

发表于 2019-05-12 | 分类于 javascript教程

javascript-字面量模板

在 ES2015(又名 ES6)中引入,字面量模板提供了一种声明字符串的新方法,但也提供了一些已经广泛流行的新有趣的结构。

字面量模板简介

字面量模板是 ES2015 / ES6 的新功能,与 ES5 及更低版本相比,它允许您以新颖的方式处理字符串。

乍一看语法非常简单,只需使用反引号而不是单引号或双引号:

JavaScript 代码:

1
const a_string = `something`;

它们是独特的,因为它们提供了许多用引号构建的普通字符串所特有的特性:

  • 它们提供了很好的语法来定义多行字符串
  • 它们提供了一种在字符串中插入变量和表达式的简便方法
  • 他们允许使用模板标签创建 DSL

让我们详细了解一下每个特性。

多行字符串

换行后每行前的空格都会保留. 无论单引号还是反引号

在 ES6 之前,要创建一个跨越两行的字符串,您必须在一行的末尾使用 \ 字符:

跨越指输入时分两行输入, 还是显示一行, 显示为两行用 \n 控制的

JavaScript 代码:

1
2
3
const string =
'first line\n \
second line';

或者

JavaScript 代码:

1
const string = 'first line\n' + 'second line';

字面量模板使多行字符串更加简单。

使用反引号打开字面量模板后,只需按 Enter 键即可创建一个没有特殊字符的新行,并按原样渲染:

JavaScript 代码:

1
2
3
4
5
const string = `Hey
this

string
is awesome!`;

请记住,空格是有意义的,所以这样做:

JavaScript 代码:

1
2
const string = `First
Second`;

上面的代码将创建一个这样的字符串:

JavaScript 代码:

1
2
First;
Second;

解决这个问题的一个简单方法是使第一行空掉,并在关闭反引号后立即跟随 trim() 方法去掉首尾空格, 中间的空格不会去,这将消除第一个字符前的任何空格:

JavaScript 代码:

1
const string = `First Second`.trim();

插值

这块讲的不怎么清楚, literals是个啥, expressions是个啥

模板标签是一种乍听起来可能不太有用的特性,但实际上它被许多流行的库所使用,比如 Styled Components 或 Apollo ,GraphQL 客户端/服务器库,因此了解它是如何工作的必不可少。

在 Styled Components 模板标签中用于定义 CSS 字符串:

JavaScript 代码:

1
2
3
4
5
const Button = styled.button`
font-size: 1.5em;
background-color: black;
color: white;
`;

在 Apollo 中,模板标签用于定义 GraphQL 查询模式:

JavaScript 代码:

1
2
3
4
5
const query = gql`
query {
...
}
`;

这些示例中 styled.button 和 gql 模板标签只是 函数 :

JavaScript 代码:

1
function gql(literals, ...expressions) {}

这个函数返回一个字符串,这个字符串可以是任何类型计算的结果。

literals 是一个数组,包含由表达式插值标记的字面量模板内容。

expressions 包含所有插值。

如果我们举一个上面的例子:

JavaScript 代码:

1
const string = `something ${1 + 2 + 3}`;

literals 是一个包含两个项的数组。 第一个是 something ,直到第一个插值的字符串,第二个是空字符串,第一个插值的结尾(我们只有一个)和字符串的结尾之间的空格。

在这种情况下,expressions 是一个包含单个项元素的数组,6 。

一个更复杂的例子是:

JavaScript 代码:

1
2
3
4
const string = `something
another ${'x'}
new line ${1 + 2 + 3}
test`;

在这种情况下,literals 是一个数组,其中第一项是:

JavaScript 代码:

1
2
`something
another `;

第二个是:

JavaScript 代码:

1
2
`
new line `;

第三个是:

JavaScript 代码:

1
2
`
test`;

在这种情况下,expressions 是一个包含两个项 x 和 6 的数组。

传递这些值的函数可以用这些值做任何事情,这就是这种特性的强大之处。

最简单的例子是通过简单地连接 literals 和 expressions 来复制字符串插值的作用:

JavaScript 代码:

1
const interpolated = interpolate`I paid ${10}€`;

这就是 interpolate 的工作原理:

JavaScript 代码:

1
2
3
4
5
6
7
8
function interpolate(literals, ...expressions) {
let string = ``;
for (const [i, val] of expressions) {
string += literals[i] + val;
}
string += literals[literals.length - 1];
return string;
}

参考

JavaScript 字面量模板(Template Literals)指南 – JavaScript 完全手册(2018 版)

javascript-引号

发表于 2019-05-11

javascript-引号

现在我们来看看 JavaScript 中允许使用的引号及其独特的特性。

JavaScript 允许您使用 3 种类型的引号:

  • 单引号(')
  • 双引号(")
  • 反引号 (`)

前 2 个基本相同:

JavaScript 代码:

1
2
const test = 'test';
const bike = 'bike';

使用这 2 种方法几乎没有差别。唯一的区别在于必须转义用于分隔字符串的引号字符:

JavaScript 代码:

1
2
3
4
5
const test = 'test';
const test = "te'st";
const test = 'te"st';
const test = 'te"st';
const test = "te'st";

有各种风格指南,建议始终使用一种风格与另一种风格。

我个人更喜欢单引号,并且只在 HTML 中使用双引号。

反引号 (`) 是 JavaScript 的最新成员,因为它们在 2015 年 ES6 才推出。

它们具有独特的功能:它们允许多行字符串。

使用转义字符,常规字符串也可以转换为多行字符串:如\n

JavaScript 代码:

1
const multilineString = 'A string\non multiple lines';

使用反引号,则可以避免使用转义字符: 直接换行就好

JavaScript 代码:

1
2
const multilineString = `A string
on multiple lines`;

不仅如此。您可以使用 ${} 语法插入变量或表达式:

JavaScript 代码:

1
2
const multilineString = `A string
on ${1 + 1} lines`;

我们将在一篇单独的文章中介绍了反引号驱动的字符串,称为字面量模板(Template Literals),它更深入地介绍了更多细节。

参考

JavaScript 中的引号 – JavaScript 完全手册(2018 版)

javascript-分号

发表于 2019-05-11 | 分类于 javascript教程

javascript-分号

参考

JavaScript 中的分号(;) – JavaScript 完全手册(2018版)

javascript-异常处理

发表于 2019-05-11 | 分类于 javascript教程

javascript-异常处理

当代码遇到异常问题时,处理这种情况的惯用 JavaScript 方法是通过异常处理。

创建异常处理

使用 throw 关键字创建一个异常:

JavaScript 代码:

1
throw value;

其中 value 可以是任何 JavaScript 值,包括字符串,数字或对象。 只要 JavaScript 执行此行,就会暂停正常的程序流,并将控件保留回最近的 异常处理程序。

异常处理

异常处理程序是 try / catch 语句。 在 try 块中包含的代码行中引发的任何异常都在相应的 catch 块中处理:

JavaScript 代码:

1
2
3
try {
//lines of code
} catch (e) {}

在此示例中, e 是异常值。

您可以添加多个处理程序,可以捕获不同类型的错误。

finally

要完成此语句,JavaScript 还有另一个名为 finally 的语句,其中包含无论是否处理了异常,是否存在异常或是否存在异常,程序流程如何都执行的代码:

JavaScript 代码:

1
2
3
4
5
try {
//lines of code
} catch (e) {
} finally {
}

您可以在没有 catch 块的情况下使用 finally ,以便清除可能在 try 语句块中打开的任何资源,如文件或网络请求:

JavaScript 代码:

1
2
3
4
try {
//lines of code
} finally {
}

嵌套 try 语句块

try 语句块可以嵌套,并且总是在最近的 catch 语句块中处理异常:

JavaScript 代码:

1
2
3
4
5
6
7
8
try {
//lines of code
try {
//other lines of code
} finally {
//other lines of code
}
} catch (e) {}

如果在内部 try 中引发异常,则在外部 catch 块中处理它。

参考

JavaScript 异常处理 – JavaScript 完全手册(2018 版)

javascript-高阶函数

发表于 2019-05-11 | 分类于 javascript教程

javascript-高阶函数

JavaScript 的一个特性使其非常适合函数式编程,它可以接受高阶函数。

高阶函数是一个函数,它可以将另一个函数作为参数,或者返回一个函数作为结果。

重点就是以上两点我们常用的.

First Class Functions

您可能听说过它将 JavaScript 视为一等公民。 这意味着 JavaScript 中的函数被视为对象。 它们具有 Object 类型,可以将它们指定为变量的值,并且可以像任何其他引用变量一样传递和返回它们。

这种原生能力在函数式编程方面赋予 JavaScript 特殊的权力。 因为函数是对象,所以该语言支持非常自然的函数式编程方法。 事实上,它是如此自然,我敢打赌你一直在使用它,甚至没有考虑它。

将函数当做参数

如果您已经完成了许多基于 Web 的 JavaScript 编程或前端开发,那么您可能会遇到使用回调的函数。回调是在完成所有其他操作后,在操作结束时执行的函数。通常,此回调函数作为函数中的最后一个参数传入。通常,它被定义为内联的匿名函数。

由于 JavaScript 是单线程的,意味着一次只发生一个操作,所以将要发生的每个操作都沿着这个单线程排队。在父函数的其余操作完成之后传入要执行的函数的策略是支持高阶函数的语言的基本特征之一。它允许异步行为,因此脚本可以在等待结果时继续执行。在处理可能在未确定的时间段之后返回结果的资源时,传递回调函数的能力是至关重要的。

单线程看 chrome的console功能

这在 Web 编程环境中非常有用,其中脚本可以将 Ajax 请求发送到服务器,然后需要在响应到达时处理响应,而不需要知道服务器上的网络延迟或处理时间。 Node.js 经常使用回调来最有效地使用服务器资源。对于在执行功能之前等待用户输入的应用程序,此方法也很有用。

例如,考虑这个简单的 JavaScript 片段,它为按钮添加了一个事件监听器。

1
2
3
4
5
<button id='clicker'>So Clickable</button>;

document.getElementById('clicker').addEventListener('click', function() {
alert('you triggered ' + this.id);
});

此脚本使用匿名内联函数来显示警报。 但它可以很容易地使用单独定义的函数,并将该命名函数传递给 addEventListener 方法

1
2
3
4
5
var proveIt = function() {
alert('you triggered ' + this.id);
};

document.getElementById('clicker').addEventListener('click', proveIt);

请注意,我们将 proveIt 而不是 proveIt() 传递给 addEventListener 函数。

当您按名称传递函数而没有括号时,您将传递函数对象本身。 当您使用括号传递它时,您将传递执行该函数的结果。

我们的 proveIt() 函数在结构上独立于它周围的代码,总是返回触发的任何元素的 id。 这段代码可以存在于您希望显示具有元素 id 的警报的任何上下文中,并且可以使用任何事件侦听器调用。

使用单独定义和命名的函数替换内联函数的能力开辟了一个充满可能性的世界。 当我们尝试开发不改变外部数据的纯函数,并且每次都为相同的输入返回相同的结果时,我们现在拥有一个必不可少的工具来帮助我们开发一个可以使用的小型目标函数库 通常用于任何应用程序。

pure function

将函数作为结果返回

除了将函数作为参数之外,JavaScript 还允许函数返回其他函数。 这很有道理,因为函数只是对象,它们可以像任何其他值一样返回。

但结果返回函数意味着什么? 将函数定义为另一个函数的返回值允许您创建可用作模板的函数来创建新函数。 这打开了另一个功能 JavaScript 魔术世界的大门。

例如,想象一下你已经厌倦了阅读所有这些关于 Millenials 的特殊性的文章,并且你决定每次发生时都想用 Millenials 代替 Snake People 这个词。 您的冲动可能只是编写一个函数,在您传递给它的任何文本上执行该文本替换:

1
2
3
4
5
var snakify = function(text) {
return text.replace(/millenials/gi, 'Snake People');
};
console.log(snakify('The Millenials are always up to something.'));
// The Snake People are always up to something.

这是有效的,但这对于这种情况非常具体。 你也厌倦了听说 Baby Boomers 。 您也想为他们制作自定义功能。 但即使有这么简单的功能,你也不想重复你编写的代码:

1
2
3
4
5
var hippify = function(text) {
return text.replace(/baby boomers/gi, 'Aging Hippies');
};
console.log(hippify('The Baby Boomers just look the other way.'));
// The Aging Hippies just look the other way.

但是如果你决定要做一些更好的东西以保留原始字符串中的情况呢? 您必须修改两个新函数才能执行此操作。 这是一个麻烦,它使你的代码更脆弱,更难阅读。

您真正想要的是能够灵活地将任何术语替换为模板函数中的任何其他术语,并将该行为定义为基础函数,您可以从中生成整个定制函数。

由于能够返回函数而不是值,JavaScript 提供了使方案更加方便的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
var attitude = function(original, replacement, source) {
return function(source) {
return source.replace(original, replacement);
};
};

var snakify = attitude(/millenials/gi, 'Snake People');
var hippify = attitude(/baby boomers/gi, 'Aging Hippies');

console.log(snakify('The Millenials are always up to something.'));
// The Snake People are always up to something.
console.log(hippify('The Baby Boomers just look the other way.'));
// The Aging Hippies just look the other way.

我们所做的是将执行实际工作的代码隔离成一个多功能且可扩展的 attitude 函数,该函数封装了使用原始短语和具有某种 attitude 的替换短语来修改任何输入字符串所需的所有工作。

定义一个新函数作为对 attitude 函数的引用,预先填充它所采用的前两个参数,允许新函数接受你传递它的任何参数,并将其用作 attutide 函数返回的内部函数中的源文本。

我们在这里利用了这样一个事实:JavaScript 函数并不关心它们是否传递了与最初定义的参数数量相同的参数。 如果缺少参数,该函数将仅将缺少的参数视为未定义。

另一方面,当被调用的函数以我们刚刚演示的方式定义时,可以在稍后传递该附加参数,作为对具有参数(或更多)的另一个函数返回的函数的引用未定义。

如果需要,可以多次查看,以便您完全了解正在发生的事情。 我们正在创建一个返回另一个函数的模板函数。 然后我们将新返回的函数(减去一个属性)定义为模板函数的自定义实现。 以这种方式创建的所有函数都将从模板函数继承相同的工作代码,但可以使用不同的默认参数进行预定义。

这就是你已经在做的事情

高阶函数是 JavaScript 工作方式的基础,你已经在使用它们了。每次传递匿名函数或回调时,实际上都会获取传递函数返回的值,并将其用作另一个函数的参数。

函数返回其他函数的能力扩展了 JavaScript 的便利性,允许我们创建自定义命名函数以使用共享模板代码执行特定任务。这些小函数中的每一个都可以继承在原始代码中所做的任何改进,这有助于我们避免代码重复,并保持我们的源清洁和可读。

作为一个额外的好处,如果你确保你的功能是纯粹的,这样它们就不会改变外部值,并且它们总是为任何给定的输入返回相同的值,你就可以自己创建测试套件以验证你的更新模板功能时,代码更改不会破坏您所依赖的任何内容。

开始考虑如何在自己的项目中利用这种方法。 JavaScript 的一大优点是,您可以将功能技术与您熟悉的代码混合使用。尝试一些实验。您可能会惊讶于使用高阶函数进行一些小工作可以轻松改进您的代码。

参考

Higher-Order Functions in JavaScript

javascript-工厂函数

发表于 2019-05-11 | 分类于 javascript教程

javascript-工厂函数

参考

JavaScript中的工厂函数

1234…14
Henry x

Henry x

this is description

133 日志
25 分类
135 标签
GitHub E-Mail
Links
  • weibo
© 2019 Henry x