1

if ( typeof v === 'undefined') {  // true
}
if ( typeof null === 'object') { // true
}

2

Number(null)  //0
5 + null  //5Number(undefined)  //NaN
5  + undefined  //NaN

3 整数和浮点数

JavaScript 语言的底层根本没有整数,所有数字都是小数(64位浮点数)。容易造成混淆的是,某些运算只有整数才能完成,此时 JavaScript 会自动把64位浮点数,转成32位整数,然后再进行运算,参见《运算符》一章的“位运算”部分。

由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。

1 === 1.0 // true0.1 + 0.2 === 0.3 // false0.3 / 0.1
// 2.9999999999999996(0.3 - 0.2) === (0.2 - 0.1)
// false

4

精度最多只能到53个二进制位,这意味着,绝对值小于2的53次方的整数,即-253到253,都可以精确表示。

Math.pow(2, 53)
// 9007199254740992Math.pow(2, 53) + 1
// 9007199254740992Math.pow(2, 53) + 2
// 9007199254740994Math.pow(2, 53) + 3
// 9007199254740996Math.pow(2, 53) + 4
// 900719925474099Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324

5 数值的进制

使用字面量(literal)直接表示一个数值时,JavaScript 对整数提供四种进制的表示方法:十进制、十六进制、八进制、二进制。十进制:没有前导0的数值。
八进制:有前缀0o或0O的数值,或者有前导0、且只用到0-7的八个阿拉伯数字的数值。
十六进制:有前缀0x或0X的数值。
二进制:有前缀0b或0B的数值。

6 正零和负零

-0 === +0 // true
0 === -0 // true
0 === +0 // true+0 // 0
-0 // 0
(-0).toString() // '0'
(+0).toString() // '0'(1 / +0) === (1 / -0) // false

7 NaN not a number

typeof NaN // 'number'NaN === NaN // false[NaN].indexOf(NaN) // -1Boolean(NaN) // falseNaN + 32 // NaN
NaN - 32 // NaN
NaN * 32 // NaN
NaN / 32 // NaN// 场景一
Math.pow(2, 1024)
// Infinity// 场景二
0 / 0 // NaN
1 / 0 // InfinityInfinity === -Infinity // false1 / -0 // -Infinity
-1 / -0 // InfinityInfinity > 1000 // true
-Infinity < -1000 // trueInfinity > NaN // false
-Infinity > NaN // falseInfinity < NaN // false
-Infinity < NaN // false

7parseInt()

如果parseInt的参数不是字符串,则会先转为字符串再转换。

parseInt(1.23) // 1
// 等同于
parseInt('1.23') // 1

字符串转为整数的时候,是一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分。

parseInt('8a') //8
parseInt('12**') // 12
parseInt('12.34') // 12
parseInt('15e2') // 15
parseInt('15px') // 15parseInt('abc') // NaN
parseInt('.3') // NaN
parseInt('') // NaN
parseInt('+') // NaN
parseInt('+1') // 1

所以,parseInt的返回值只有两种可能,要么是一个十进制整数,要么是NaN。
如果字符串以0x或0X开头,parseInt会将其按照十六进制数解析。

paeseInt('0x10') //16

如果字符串以0开头,将其按照10进制解析。

parseInt('011') //11

对于那些会自动转为科学计数法的数字,parseInt会将科学计数法的表示方法视为字符串,因此导致一些奇怪的结果。

parseInt(1000000000000000000000.5) // 1
// 等同于
parseInt('1e+21') // 1parseInt(0.0000008) // 8
// 等同于
parseInt('8e-7') // 8

parseInt方法还可以接受第二个参数(2到36之间),表示被解析的值的进制,返回该值对应的十进制数。默认情况下,parseInt的第二个参数为10,即默认是十进制转十进制。

parseInt('1000') // 1000
// 等同于
parseInt('1000', 10) // 1000parseInt('1000', 2) // 8
parseInt('1000', 6) // 216
parseInt('1000', 8) // 512

如果第二个参数不是数值,会被自动转为一个整数。这个整数只有在2到36之间,才能得到有意义的结果,超出这个范围,则返回NaN。如果第二个参数是0、undefined和null,则直接忽略。

parseInt('10', 37) // NaN
parseInt('10', 1) // NaN
parseInt('10', 0) // 10
parseInt('10', null) // 10
parseInt('10', undefined) // 10

如果字符串包含对于指定进制无意义的字符,则从最高位开始,只返回可以转换的数值。如果最高位无法转换,则直接返回NaN。

parseInt('1546', 2) // 1
parseInt('546', 2) // NaN

前面说过,如果parseInt的第一个参数不是字符串,会被先转为字符串。这会导致一些令人意外的结果。

parseInt(0x11, 36) // 43
parseInt(0x11, 2) // 1// 等同于
parseInt(String(0x11), 36)
parseInt(String(0x11), 2)// 等同于
parseInt('17', 36)
parseInt('17', 2)

上面代码中,十六进制的0x11会被先转为十进制的17,再转为字符串。然后,再用36进制或二进制解读字符串17,最后返回结果43和1。

这种处理方式,对于八进制的前缀0,尤其需要注意。


parseInt(011, 2) // NaN// 等同于
parseInt(String(011), 2)// 等同于
parseInt(String(9), 2)

上面代码中,第一行的011会被先转为字符串9,因为9不是二进制的有效字符,所以返回NaN。如果直接计算parseInt(‘011’, 2),011则是会被当作二进制处理,返回3。

JavaScript 不再允许将带有前缀0的数字视为八进制数,而是要求忽略这个0。但是,为了保证兼容性,大部分浏览器并没有部署这一条规定。

8parseFloat

parseFloat(true)  // NaN
Number(true) // 1parseFloat(null) // NaN
Number(null) // 0parseFloat('') // NaN
Number('') // 0parseFloat('123.45#') // 123.45
Number('123.45#') // NaN

9 isNaN

但是,对于空数组和只有一个数值成员的数组,isNaN返回false。

isNaN([]) // false
isNaN([123]) // false
isNaN(['123']) // false

上面代码之所以返回false,原因是这些数组能被Number函数转成数值,请参见《数据类型转换》一章。

因此,使用isNaN之前,最好判断一下数据类型。

function myIsNaN(value) {return typeof value === 'number' && isNaN(value);
}

判断NaN更可靠的方法是,利用NaN为唯一不等于自身的值的这个特点,进行判断。

function myIsNaN(value) {return value !== value;
}

10 标识符

下面这些都是合法的标识符。arg0
_tmp
$elem
π
下面这些则是不合法的标识符。1a  // 第一个字符不能是数字
23  // 同上
***  // 标识符不能包含星号
a+b  // 标识符不能包含加号
-d  // 标识符不能包含减号或连词线
中文是合法的标识符,可以用作变量名。var 临时变量 = 1;
JavaScript 有一些保留字,不能用作标识符:arguments、break、case、catch、class、const、continue、debugger、default、delete、do、else、enum、eval、export、extends、false、finally、for、function、if、implements、import、in、instanceof、interface、let、new、null、package、private、protected、public、return、static、super、switch、this、throw、true、try、typeof、var、void、while、with、yield。

11 字符串

如果长字符串必须分成多行,可以在每一行的尾部使用反斜杠。

var longString = 'Long \
long \
long \
string';longString
// "Long long long string"

上面代码表示,加了反斜杠以后,原来写在一行的字符串,可以分成多行书写。但是,输出的时候还是单行,效果与写在同一行完全一样。注意,反斜杠的后面必须是换行符,而不能有其他字符(比如空格),否则会报错。
连接运算符(+)可以连接多个单行字符串,将长字符串拆成多行书写,输出的时候也是单行

var longString = 'Long '+ 'long '+ 'long '+ 'string';

如果想输出多行字符串,有一种利用多行注释的变通方法。

(function () { /*
line 1
line 2
line 3
*/}).toString().split('\n').slice(1, -1).join('\n')
// "line 1
// line 2
// line 3"

反斜杠(\)在字符串内有特殊含义,用来表示一些特殊字符,所以又称为转义符。

需要用反斜杠转义的特殊字符,主要有下面这些。

\0 :null(\u0000)
\b :后退键(\u0008)
\f :换页符(\u000C)
\n :换行符(\u000A)
\r :回车键(\u000D)
\t :制表符(\u0009)
\v :垂直制表符(\u000B)
\' :单引号(\u0027)
\" :双引号(\u0022)
\\ :反斜杠(\u005C)

字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始)。

var s = 'hello';
s[0] // "h"
s[1] // "e"
s[4] // "o"// 直接对字符串使用方括号运算符
'hello'[1] // "e"

但是,字符串与数组的相似性仅此而已。实际上,无法改变字符串之中的单个字符。

var s = 'hello';delete s[0];
s // "hello"s[1] = 'a';
s // "hello"s[5] = '!';
s // "hello"

12 Base64 转码

JavaScript 原生提供两个 Base64 相关的方法。
btoa():任意值转为 Base64 编码
atob():Base64 编码转为原来的值

var string = 'Hello World!';
btoa(string) // "SGVsbG8gV29ybGQh"
atob('SGVsbG8gV29ybGQh') // "Hello World!"

注意,这两个方法不适合非 ASCII 码的字符,会报错。

btoa('你好') // 报错

要将非 ASCII 码字符转为 Base64 编码,必须中间插入一个转码环节,再使用这两个方法。

function b64Encode(str) {return btoa(encodeURIComponent(str));
}function b64Decode(str) {return decodeURIComponent(atob(str));
}b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"

13 object

var obj = {};
if ('toString' in obj) {console.log(obj.hasOwnProperty('toString')) // false
}

for…in循环有两个使用注意点。

它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
它不仅遍历对象自身的属性,还遍历继承的属性。
举例来说,对象都继承了toString属性,但是for…in循环不会遍历到这个属性。

var person = { name: '老张' };for (var key in person) {if (person.hasOwnProperty(key)) {console.log(key);}
}
// name

14 function

上面代码第二行,调用f的时候,f只是被声明了,还没有被赋值,等于undefined,所以会报错。因此,如果同时采用function命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。

var f = function () {console.log('1');
}function f() {console.log('2');
}f() // 1

函数的toString方法返回一个字符串,内容是函数的源码。

function f() {a();b();c();
}f.toString()
// function f() {
//  a();
//  b();
//  c();
// }

对于顶层函数来说,函数外部声明的变量就是全局变量(global variable),它可以在函数内部读取。

var v = 1;function f() {console.log(v);
}f()
// 1

上面的代码表明,函数f内部可以读取全局变量v。

在函数内部定义的变量,外部无法读取,称为“局部变量”(local variable)。

function f(){var v = 1;
}v // ReferenceError: v is not defined

上面代码中,变量v在函数内部定义,所以是一个局部变量,函数之外就无法读取。

函数内部定义的变量,会在该作用域内覆盖同名全局变量。

var v = 1;function f(){var v = 2;console.log(v);
}f() // 2
v // 1

上面代码中,变量v同时在函数的外部和内部有定义。结果,在函数内部定义,局部变量v覆盖了全局变量v。

注意,对于var命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。
在其他区块中声明,一律都是全局变量。
在其他区块中声明,一律都是全局变量。

if (true) {var x = 5;
}
console.log(x);  // 5

上面代码中,变量x在条件判断区块之中声明,结果就是一个全局变量,可以在区块之外读取。

函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。

arguments
需要注意的是,虽然arguments很像数组,但它是一个对象。数组专有的方法(比如slice和forEach),不能在arguments对象上直接使用。

如果要让arguments对象使用数组方法,真正的解决方法是将arguments转为真正的数组。下面是两种常用的转换方法:slice方法和逐一填入新数组。

var args = Array.prototype.slice.call(arguments);// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {args.push(arguments[i]);
}

闭包
闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。

function createIncrementor(start) {return function () {return start++;};
}var inc = createIncrementor(5);inc() // 5
inc() // 6
inc() // 7

上面代码中,start是函数createIncrementor的内部变量。通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc使得函数createIncrementor的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。

为什么会这样呢?原因就在于inc始终在内存中,而inc的存在依赖于createIncrementor,因此也始终在内存中,不会在调用结束后,被垃圾回收机制回收。

闭包的另一个用处,是封装对象的私有属性和私有方法。

立即调用的函数表达式(IIFE)
有时,我们需要在定义函数之后,立即调用该函数。这时,你不能在函数的定义之后加上圆括号,这会产生语法错误。

function(){ /* code */ }();
// SyntaxError: Unexpected token (

产生这个错误的原因是,function这个关键字即可以当作语句,也可以当作表达式。

// 语句
function f() {}// 表达式
var f = function f() {}

为了避免解析上的歧义,JavaScript 引擎规定,如果function关键字出现在行首,一律解释成语句。因此,JavaScript 引擎看到行首是function关键字之后,认为这一段都是函数的定义,不应该以圆括号结尾,所以就报错了。

解决方法就是不要让function出现在行首,让引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里面。

(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();

上面两种写法都是以圆括号开头,引擎就会认为后面跟的是一个表示式,而不是函数定义语句,所以就避免了错误。这就叫做“立即调用的函数表达式”(Immediately-Invoked Function Expression),简称 IIFE。

注意,上面两种写法最后的分号都是必须的。如果省略分号,遇到连着两个 IIFE,可能就会报错。

// 报错
(function(){ /* code */ }())
(function(){ /* code */ }())

上面代码的两行之间没有分号,JavaScript 会将它们连在一起解释,将第二行解释为第一行的参数。

推而广之,任何让解释器以表达式来处理函数定义的方法,都能产生同样的效果,比如下面三种写法。

var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();

甚至像下面这样写,也是可以的。

!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();

通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。


// 写法一
var tmp = newData;
processData(tmp);
storeData(tmp);// 写法二
(function () {var tmp = newData;processData(tmp);storeData(tmp);
}());

上面代码中,写法二比写法一更好,因为完全避免了污染全局变量。

15 数组

// 设置负值
[].length = -1
// RangeError: Invalid array length// 数组元素个数大于等于2的32次方
[].length = Math.pow(2, 32)
// RangeError: Invalid array length// 设置字符串
[].length = 'abc'
// RangeError: Invalid array length

值得注意的是,由于数组本质上是一种对象,所以可以为数组添加属性,但是这不影响length属性的值。


var a = [];a['p'] = 'abc';
a.length // 0a[2.1] = 'abc';
a.length // 0

上面代码将数组的键分别设为字符串和小数,结果都不影响length属性。因为,length属性的值就是等于最大的数字键加1,而这个数组没有整数键,所以length属性保持为0。

如果数组的键名是添加超出范围的数值,该键名会自动转为字符串。


var arr = [];
arr[-1] = 'a';
arr[Math.pow(2, 32)] = 'b';arr.length // 0
arr[-1] // "a"
arr[4294967296] // "b"

上面代码中,我们为数组arr添加了两个不合法的数字键,结果length属性没有发生变化。这些数字键都变成了字符串键名。最后两行之所以会取到值,是因为取键值时,数字键名会默认转为字符串。

数组的slice方法可以将“类似数组的对象”变成真正的数组。

var arr = Array.prototype.slice.call(arrayLike);

除了转为真正的数组,“类似数组的对象”还有一个办法可以使用数组的方法,就是通过call()把数组的方法放到对象上面。

function print(value, index) {console.log(index + ' : ' + value);
}Array.prototype.forEach.call(arrayLike, print);

上面代码中,arrayLike代表一个类似数组的对象,本来是不可以使用数组的forEach()方法的,但是通过call(),可以把forEach()嫁接到arrayLike上面调用。

下面的例子就是通过这种方法,在arguments对象上面调用forEach方法。

// forEach 方法
function logArgs() {Array.prototype.forEach.call(arguments, function (elem, i) {console.log(i + '. ' + elem);});
}// 等同于 for 循环
function logArgs() {for (var i = 0; i < arguments.length; i++) {console.log(i + '. ' + arguments[i]);}
}

字符串也是类似数组的对象,所以也可以用Array.prototype.forEach.call遍历。

Array.prototype.forEach.call('abc', function (chr) {console.log(chr);
});
// a
// b
// c

注意,这种方法比直接使用数组原生的forEach要慢,所以最好还是先将“类似数组的对象”转为真正的数组,然后再直接调用数组的forEach方法。

var arr = Array.prototype.slice.call('abc');
arr.forEach(function (chr) {console.log(chr);
});
// a
// b
// c

16 比较

NaN === NaN  // false
+0 === -0 // trueundefined === undefined // true
null === null // true

两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个地址。

{} === {} // false
[] === [] // false
(function () {} === function () {}) // false

如果两个变量引用同一个对象,则它们相等。

var v1 = {};
var v2 = v1;
v1 === v2 // true
1 == 1.0
// 等同于
1 === 1.0

上面代码中,数组[1]与数值进行比较,会先转成数值,再进行比较;与字符串进行比较,会先转成字符串,再进行比较;与布尔值进行比较,对象和布尔值都会先转成数值,再进行比较。

17 void

请看下面的代码。

<script>
function f() {console.log('Hello World');
}
</script>
<a href="http://example.com" onclick="f(); return false;">点击</a>

上面代码中,点击链接后,会先执行onclick的代码,由于onclick返回false,所以浏览器不会跳转到 example.com。

void运算符可以取代上面的写法。

<a href="javascript: void(f())">文字</a>
下面是一个更实际的例子,用户点击链接提交表单,但是不产生页面跳转。<a href="javascript: void(document.form.submit())">提交
</a>

18 数据类型的转换

// 数值:转换后还是原来的值
Number(324) // 324// 字符串:如果可以被解析为数值,则转换为相应的数值
Number('324') // 324// 字符串:如果不可以被解析为数值,返回 NaN
Number('324abc') // NaN// 空字符串转为0
Number('') // 0// 布尔值:true 转成 1,false 转成 0
Number(true) // 1
Number(false) // 0// undefined:转成 NaN
Number(undefined) // NaN// null:转成0
Number(null) // 0

19 try…catch…finally

function f() {try {console.log(0);throw 'bug';} catch(e) {console.log(1);return true; // 这句原本会延迟到 finally 代码块结束再执行console.log(2); // 不会运行} finally {console.log(3);return false; // 这句会覆盖掉前面那句 returnconsole.log(4); // 不会运行}console.log(5); // 不会运行
}var result = f();
// 0
// 1
// 3result
// false

20 Object

var obj = Object(1);
obj instanceof Object // true
obj instanceof Number // truevar obj = Object('foo');
obj instanceof Object // true
obj instanceof String // truevar obj = Object(true);
obj instanceof Object // true
obj instanceof Boolean // true

上面代码中,Object函数的参数是各种原始类型的值,转换成对象就是原始类型值对应的包装对象。

如果Object方法的参数是一个对象,它总是返回该对象,即不用转换。

var arr = [];
var obj = Object(arr); // 返回原数组
obj === arr // truevar value = {};
var obj = Object(value) // 返回原对象
obj === value // truevar fn = function () {};
var obj = Object(fn); // 返回原函数
obj === fn // true

利用这一点,可以写一个判断变量是否为对象的函数。

function isObject(value) {return value === Object(value);
}isObject([]) // true
isObject(true) // false

对于一般的对象来说,Object.keys()和Object.getOwnPropertyNames()返回的结果是一样的。只有涉及不可枚举属性时,才会有不一样的结果。Object.keys方法只返回可枚举的属性(详见《对象属性的描述对象》一章),Object.getOwnPropertyNames方法还返回不可枚举的属性名。

var a = ['Hello', 'World'];Object.keys(a) // ["0", "1"]
Object.getOwnPropertyNames(a) // ["0", "1", "length"]

上面代码中,数组的length属性是不可枚举的属性,所以只出现在Object.getOwnPropertyNames方法的返回结果中。

由于 JavaScript 没有提供计算对象属性个数的方法,所以可以用这两个方法代替。

var obj = {p1: 123,p2: 456
};Object.keys(obj).length // 2
Object.getOwnPropertyNames(obj).length // 2

一般情况下,几乎总是使用Object.keys方法,遍历对象的属性。

这就是说,Object.prototype.toString可以看出一个值到底是什么类型。

Object.prototype.toString.call(2) // "[object Number]"
Object.prototype.toString.call('') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(Math) // "[object Math]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call([]) // "[object Array]"

利用这个特性,可以写出一个比typeof运算符更准确的类型判断函数。

var type = function (o){var s = Object.prototype.toString.call(o);return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};type({}); // "object"
type([]); // "array"
type(5); // "number"
type(null); // "null"
type(); // "undefined"
type(/abcd/); // "regex"
type(new Date()); // "date"

在上面这个type函数的基础上,还可以加上专门判断某种类型数据的方法。

var type = function (o){var s = Object.prototype.toString.call(o);return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};['Null','Undefined','Object','Array','String','Number','Boolean','Function','RegExp'
].forEach(function (t) {type['is' + t] = function (o) {return type(o) === t.toLowerCase();};
});type.isObject({}) // true
type.isNumber(NaN) // true
type.isRegExp(/abc/) // true

Object.defineProperty()方法允许通过属性描述对象,定义或修改一个属性,然后返回修改后的对象,它的用法如下。

Object.defineProperty(object, propertyName, attributesObject)

Object.defineProperty方法接受三个参数,依次如下。

object:属性所在的对象
propertyName:字符串,表示属性名
attributesObject:属性描述对象
举例来说,定义obj.p可以写成下面这样。

var obj = Object.defineProperty({}, 'p', {value: 123,writable: false,enumerable: true,configurable: false
});obj.p // 123obj.p = 246;
obj.p // 123

如果一次性定义或修改多个属性,可以使用Object.defineProperties()方法。

var obj = Object.defineProperties({}, {p1: { value: 123, enumerable: true },p2: { value: 'abc', enumerable: true },p3: { get: function () { return this.p1 + this.p2 },enumerable:true,configurable:true}
});obj.p1 // 123
obj.p2 // "abc"
obj.p3 // "123abc"

对象的拷贝
有时,我们需要将一个对象的所有属性,拷贝到另一个对象,可以用下面的方法实现。

var extend = function (to, from) {for (var property in from) {to[property] = from[property];}return to;
}extend({}, {a: 1
})
// {a: 1}

上面这个方法的问题在于,如果遇到存取器定义的属性,会只拷贝值。

extend({}, {get a() { return 1 }
})
// {a: 1}

为了解决这个问题,我们可以通过Object.defineProperty方法来拷贝属性。

var extend = function (to, from) {for (var property in from) {if (!from.hasOwnProperty(property)) continue;Object.defineProperty(to,property,Object.getOwnPropertyDescriptor(from, property));}return to;
}extend({}, { get a(){ return 1 } })
// { get a(){ return 1 } })

上面代码中,hasOwnProperty那一行用来过滤掉继承的属性,否则可能会报错,因为Object.getOwnPropertyDescriptor读不到继承属性的属性描述对象。

有时需要冻结对象的读写状态,防止对象被改变。JavaScript 提供了三种冻结方法,最弱的一种是Object.preventExtensions,其次是Object.seal,最强的是Object.freeze。

21 Array

Array.isArray方法返回一个布尔值,表示参数是否为数组。它可以弥补typeof运算符的不足。

var arr = [1, 2, 3];typeof arr // "object"
Array.isArray(arr) // true

valueOf方法是一个所有对象都拥有的方法,表示对该对象求值。不同对象的valueOf方法不尽一致,数组的valueOf方法返回数组本身。


var arr = [1, 2, 3];
arr.valueOf() // [1, 2, 3]

toString方法也是对象的通用方法,数组的toString方法返回数组的字符串形式。

var arr = [1, 2, 3];
arr.toString() // "1,2,3"var arr = [1, 2, 3, [4, 5, 6]];
arr.toString() // "1,2,3,4,5,6"

push方法用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组。
pop方法用于删除数组的最后一个元素,并返回该元素。注意,该方法会改变原数组。
shift()方法用于删除数组的第一个元素,并返回该元素。注意,该方法会改变原数组。
unshift()方法用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组。

push和pop结合使用,就构成了“后进先出”的栈结构(stack)。
push()和shift()结合使用,就构成了“先进先出”的队列结构(queue)。

var arr = [];
arr.push(1, 2);
arr.push(3);
arr.pop();
arr // [1, 2]var a  = ['1','2]
a.unshift('3')
a // [3,1,2]

join()方法以指定参数作为分隔符,将所有数组成员连接为一个字符串返回。如果不提供参数,默认用逗号分隔。

var a = [1, 2, 3, 4];a.join(' ') // '1 2 3 4'
a.join(' | ') // "1 | 2 | 3 | 4"
a.join() // "1,2,3,4"

如果数组成员是undefined或null或空位,会被转成空字符串。

[undefined, null].join('#')
// '#'['a',, 'b'].join('-')
// 'a--b'

通过call方法,这个方法也可以用于字符串或类似数组的对象。

Array.prototype.join.call('hello', '-')
// "h-e-l-l-o"var obj = { 0: 'a', 1: 'b', length: 2 };
Array.prototype.join.call(obj, '-')
// 'a-b'

concat
如果数组成员包括对象,concat方法返回当前数组的一个浅拷贝。所谓“浅拷贝”,指的是新数组拷贝的是对象的引用。

var obj = { a: 1 };
var oldArray = [obj];var newArray = oldArray.concat();obj.a = 2;
newArray[0].a // 2

slice方法用于提取目标数组的一部分,返回一个新数组,原数组不变。

arr.slice(start, end)
;
var a = ['a', 'b', 'c'];a.slice(0) // ["a", "b", "c"]
a.slice(1) // ["b", "c"]
a.slice(1, 2) // ["b"]
a.slice(2, 6) // ["c"]
a.slice() // ["a", "b", "c"]

它的第一个参数为起始位置(从0开始),第二个参数为终止位置(但该位置的元素本身不包括在内)。如果省略第二个参数,则一直返回到原数组的最后一个成员。

slice方法的一个重要应用,是将类似数组的对象转为真正的数组。

Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 })
// ['a', 'b']Array.prototype.slice.call(document.querySelectorAll("div"));
Array.prototype.slice.call(arguments);

splice方法用于删除原数组的一部分成员,并可以在删除的位置添加新的数组成员,返回值是被删除的元素。注意,该方法会改变原数组。

arr.splice(start, count, addElement1, addElement2, ...);

splice的第一个参数是删除的起始位置(从0开始),第二个参数是被删除的元素个数。如果后面还有更多的参数,则表示这些就是要被插入数组的新元素。

如果只是单纯地插入元素,splice方法的第二个参数可以设为0。

var a = [1, 1, 1];a.splice(1, 0, 2) // []
a // [1, 2, 1, 1]

删除并插入数据

var a = ['a', 'b', 'c', 'd', 'e', 'f'];
a.splice(4, 2, 1, 2) // ["e", "f"]
a // ["a", "b", "c", "d", 1, 2]

sort方法对数组成员进行排序,默认是按照字典顺序排序。排序后,原数组将被改变。

['d', 'c', 'b', 'a'].sort()
// ['a', 'b', 'c', 'd'][4, 3, 2, 1].sort()
// [1, 2, 3, 4][11, 101].sort()
// [101, 11][10111, 1101, 111].sort()
// [10111, 1101, 111]

上面代码的最后两个例子,需要特殊注意。sort()方法不是按照大小排序,而是按照字典顺序。也就是说,数值会被先转成字符串,再按照字典顺序进行比较,所以101排在11的前面。

如果想让sort方法按照自定义方式排序,可以传入一个函数作为参数。

[10111, 1101, 111].sort(function (a, b) {return a - b;
})
// [111, 1101, 10111]

map方法将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回

[1, 2, 3].map(function(elem, index, arr) {return elem * index;
});
// [0, 2, 6]

map方法还可以接受第二个参数,用来绑定回调函数内部的this变量

var arr = ['a', 'b', 'c'];[1, 2].map(function (e) {return this[e];
}, arr)
// ['b', 'c']

forEach方法与map方法很相似,也是对数组的所有成员依次执行参数函数。但是,forEach方法不返回值,只用来操作数据。这就是说,如果数组遍历的目的是为了得到返回值,那么使用map方法,否则使用forEach方法。

forEach方法也可以接受第二个参数,绑定参数函数的this变量。

var out = [];[1, 2, 3].forEach(function(elem) {this.push(elem * elem);
}, out);out // [1, 4, 9]

注意,forEach方法无法中断执行,总是会将所有成员遍历完。如果希望符合某种条件时,就中断遍历,要使用for循环。

var arr = [1, 2, 3];for (var i = 0; i < arr.length; i++) {if (arr[i] === 2) break;console.log(arr[i]);
}
// 1

filter方法用于过滤数组成员,满足条件的成员组成一个新数组返回。
reduce方法和reduceRight方法依次处理数组的每个成员,最终累计为一个值。它们的差别是,reduce是从左到右处理(从第一个成员到最后一个成员),reduceRight则是从右到左(从最后一个成员到第一个成员),其他完全一样。

[1, 2, 3, 4, 5].reduce(function (a, b) {console.log(a, b);return a + b;
})
// 1 2
// 3 3
// 6 4
// 10 5
//最后结果:15

reduce方法和reduceRight方法的第一个参数都是一个函数。该函数接受以下四个参数。

累积变量,默认为数组的第一个成员
当前变量,默认为数组的第二个成员
当前位置(从0开始)
原数组
这四个参数之中,只有前两个是必须的,后两个则是可选的。

如果要对累积变量指定初值,可以把它放在reduce方法和reduceRight方法的第二个参数。

[1, 2, 3, 4, 5].reduce(function (a, b) {return a + b;
}, 10);
// 25

上面代码指定参数a的初值为10,所以数组从10开始累加,最终结果为25。注意,这时b是从数组的第一个成员开始遍历。

由于这两个方法会遍历数组,所以实际上还可以用来做一些遍历相关的操作。比如,找出字符长度最长的数组成员。

function findLongest(entries) {return entries.reduce(function (longest, entry) {return entry.length > longest.length ? entry : longest;}, '');
}findLongest(['aaa', 'bb', 'c']) // "aaa"

上面代码中,reduce的参数函数会将字符长度较长的那个数组成员,作为累积值。这导致遍历所有成员之后,累积值就是字符长度最长的那个成员。

indexOf方法返回给定元素在数组中第一次出现的位置,如果没有出现则返回-1。

var a = ['a', 'b', 'c'];a.indexOf('b') // 1
a.indexOf('y') // -1

indexOf方法还可以接受第二个参数,表示搜索的开始位置。

['a', 'b', 'c'].indexOf('a', 1) // -1

上面代码从1号位置开始搜索字符a,结果为-1,表示没有搜索到。

lastIndexOf方法返回给定元素在数组中最后一次出现的位置,如果没有出现则返回-1。

var a = [2, 5, 9, 2];
a.lastIndexOf(2) // 3
a.lastIndexOf(7) // -1

注意,这两个方法不能用来搜索NaN的位置,即它们无法确定数组成员是否包含NaN。

[NaN].indexOf(NaN) // -1
[NaN].lastIndexOf(NaN) // -1

这是因为这两个方法内部,使用严格相等运算符(===)进行比较,而NaN是唯一一个不等于自身的值。

22 Number

(123).toLocaleString('zh-Hans-CN', { style: 'currency', currency: 'CNY' })
// "¥123.00"

23 String

var s1 = 'abc';
var s2 = new String('abc');typeof s1 // "string"
typeof s2 // "object"s2.valueOf() // "abc"

这种现象的根本原因在于,码点大于0xFFFF的字符占用四个字节,而 JavaScript 默认支持两个字节的字符。这种情况下,必须把0x20BB7拆成两个字符表示。

String.fromCharCode(0xD842, 0xDFB7)
// "𠮷"

slice方法用于从原字符串取出子字符串并返回,不改变原字符串。它的第一个参数是子字符串的开始位置,第二个参数是子字符串的结束位置(不含该位置)。

'JavaScript'.slice(0, 4) // "Java"

如果省略第二个参数,则表示子字符串一直到原字符串结束。

'JavaScript'.slice(4) // "Script"

如果参数是负值,表示从结尾开始倒数计算的位置,即该负值加上字符串长度。

'JavaScript'.slice(-6) // "Script"
'JavaScript'.slice(0, -6) // "Java"
'JavaScript'.slice(-2, -1) // "p"

如果第一个参数大于第二个参数,slice方法返回一个空字符串。

'JavaScript'.slice(2, 1) // ""

substring方法用于从原字符串取出子字符串并返回,不改变原字符串,跟slice方法很相像。它的第一个参数表示子字符串的开始位置,第二个位置表示结束位置(返回结果不含该位置)。

'JavaScript'.substring(0, 4) // "Java"

如果省略第二个参数,则表示子字符串一直到原字符串的结束。

'JavaScript'.substring(4) // "Script"

如果第一个参数大于第二个参数,substring方法会自动更换两个参数的位置。

'JavaScript'.substring(10, 4) // "Script"
// 等同于
'JavaScript'.substring(4, 10) // "Script"

上面代码中,调换substring方法的两个参数,都得到同样的结果。

如果参数是负数,substring方法会自动将负数转为0。

'JavaScript'.substring(-3) // "JavaScript"
'JavaScript'.substring(4, -3) // "Java"

上面代码中,第二个例子的参数-3会自动变成0,等同于’JavaScript’.substring(4, 0)。由于第二个参数小于第一个参数,会自动互换位置,所以返回Java。

由于这些规则违反直觉,因此不建议使用substring方法,应该优先使用slice。

substr方法用于从原字符串取出子字符串并返回,不改变原字符串,跟slice和substring方法的作用相同。

substr方法的第一个参数是子字符串的开始位置(从0开始计算),第二个参数是子字符串的长度。

'JavaScript'.substr(4, 6) // "Script"

如果省略第二个参数,则表示子字符串一直到原字符串的结束。

'JavaScript'.substr(4) // "Script"

如果第一个参数是负数,表示倒数计算的字符位置。如果第二个参数是负数,将被自动转为0,因此会返回空字符串。

'JavaScript'.substr(-6) // "Script"
'JavaScript'.substr(4, -1) // ""

split方法还可以接受第二个参数,限定返回数组的最大成员数。

'a|b|c'.split('|', 0) // []
'a|b|c'.split('|', 1) // ["a"]
'a|b|c'.split('|', 2) // ["a", "b"]
'a|b|c'.split('|', 3) // ["a", "b", "c"]
'a|b|c'.split('|', 4) // ["a", "b", "c"]

24 Math

下面是计算圆面积的方法。

var radius = 20;
var area = Math.PI * Math.pow(radius, 2);

Math.random()
任意范围的随机数生成函数如下。

function getRandomArbitrary(min, max) {return Math.random() * (max - min) + min;
}getRandomArbitrary(1.5, 6.5)
// 2.4942810038223864

任意范围的随机整数生成函数如下。

function getRandomInt(min, max) {return Math.floor(Math.random() * (max - min + 1)) + min;
}getRandomInt(1, 6) // 5

25 Date

只要是能被Date.parse()方法解析的字符串,都可以当作参数。

new Date('2013-2-15')
new Date('2013/2/15')
new Date('02/15/2013')
new Date('2013-FEB-15')
new Date('FEB, 15, 2013')
new Date('FEB 15, 2013')
new Date('February, 15, 2013')
new Date('February 15, 2013')
new Date('15 Feb 2013')
new Date('15, February, 2013')
// Fri Feb 15 2013 00:00:00 GMT+0800 (CST)

以下三种方法,可以将 Date 实例转为表示本地时间的字符串。

Date.prototype.toLocaleString():完整的本地时间。
Date.prototype.toLocaleDateString():本地日期(不含小时、分和秒)。
Date.prototype.toLocaleTimeString():本地时间(不含年月日)。
下面是用法实例。

var d = new Date(2013, 0, 1);d.toLocaleString()
// 中文版浏览器为"2013年1月1日 上午12:00:00"
// 英文版浏览器为"1/1/2013 12:00:00 AM"d.toLocaleDateString()
// 中文版浏览器为"2013年1月1日"
// 英文版浏览器为"1/1/2013"d.toLocaleTimeString()
// 中文版浏览器为"上午12:00:00"
// 英文版浏览器为"12:00:00 AM"

26 RegExp

正则实例对象的test方法返回一个布尔值,表示当前模式是否能匹配参数字符串。

/cat/.test('cats and dogs') // true

上面代码验证参数字符串之中是否包含cat,结果返回true。

如果正则表达式带有g修饰符,则每一次test方法都从上一次结束的位置开始向后匹配。


var r = /x/g;
var s = '_x_x';r.lastIndex // 0
r.test(s) // truer.lastIndex // 2
r.test(s) // truer.lastIndex // 4
r.test(s) // false

正则实例对象的exec方法,用来返回匹配结果。如果发现匹配,就返回一个数组,成员是匹配成功的子字符串,否则返回null。

var s = '_x_x';
var r1 = /x/;
var r2 = /y/;r1.exec(s) // ["x"]
r2.exec(s) // null

字符串的实例方法之中,有4种与正则表达式有关。

String.prototype.match():返回一个数组,成员是所有匹配的子字符串。
String.prototype.search():按照给定的正则表达式进行搜索,返回一个整数,表示匹配开始的位置。
String.prototype.replace():按照给定的正则表达式进行替换,返回替换后的字符串。
String.prototype.split():按照给定规则进行字符串分割,返回一个数组,包含分割后的各个成员。

match

var s = 'abba';
var r = /a/g;s.match(r) // ["a", "a"]
r.exec(s) // ["a"]var s = 'abab'
var r = /a/g;

字符串对象的replace方法可以替换匹配的值。它接受两个参数,第一个是正则表达式,表示搜索模式,第二个是替换的内容。

str.replace(search, replacement)

正则表达式如果不加g修饰符,就替换第一个匹配成功的值,否则替换所有匹配成功的值。


'aaa'.replace('a', 'b') // "baa"
'aaa'.replace(/a/, 'b') // "baa"
'aaa'.replace(/a/g, 'b') // "bbb"

replace方法的一个应用,就是消除字符串首尾两端的空格。

var str = '  #id div.class  ';
str.replace(/^\s+|\s+$/g, '')
// "#id div.class"

replace方法的第二个参数可以使用美元符号$,用来指代所替换的内容。

$&:匹配的子字符串。
$`:匹配结果前面的文本。
$’:匹配结果后面的文本。
$n:匹配成功的第n组内容,n是从1开始的自然数。
$:指代美元符号

'hello world'.replace(/(\w+)\s(\w+)/, '$2 $1')
// "world hello"'abc'.replace('b', '[$`-$&-$\']')
// "a[a-b-c]c"

上面代码中,第一个例子是将匹配的组互换位置,第二个例子是改写匹配的值。

replace方法的第二个参数还可以是一个函数,将每一个匹配内容替换为函数返回值。

'3 and 5'.replace(/[0-9]+/g, function (match) {return 2 * match;
})
// "6 and 10"var a = 'The quick brown fox jumped over the lazy dog.';
var pattern = /quick|brown|lazy/ig;a.replace(pattern, function replacer(match) {return match.toUpperCase();
});
// The QUICK BROWN fox jumped over the LAZY dog.

作为replace方法第二个参数的替换函数,可以接受多个参数。其中,第一个参数是捕捉到的内容,第二个参数是捕捉到的组匹配(有多少个组匹配,就有多少个对应的参数)。此外,最后还可以添加两个参数,倒数第二个参数是捕捉到的内容在整个字符串中的位置(比如从第五个位置开始),最后一个参数是原字符串。下面是一个网页模板替换的例子。

var prices = {'p1': '$1.99','p2': '$9.99','p3': '$5.00'
};var template = '<span id="p1"></span>'+ '<span id="p2"></span>'+ '<span id="p3"></span>';template.replace(/(<span id=")(.*?)(">)(<\/span>)/g,function(match, $1, $2, $3, $4){return $1 + $2 + $3 + prices[$2] + $4;}
);
// "<span id="p1">$1.99</span><span id="p2">$9.99</span><span id="p3">$5.00</span>"

上面代码的捕捉模式中,有四个括号,所以会产生四个组匹配,在匹配函数中用$1到$4表示。匹配函数的作用是将价格插入模板中。

字符串对象的split方法按照正则规则分割字符串,返回一个由分割后的各个部分组成的数组。

str.split(separator, [limit])

该方法接受两个参数,第一个参数是正则表达式,表示分隔规则,第二个参数是返回数组的最大成员数。

// 非正则分隔
'a,  b,c, d'.split(',')
// [ 'a', '  b', 'c', ' d' ]// 正则分隔,去除多余的空格
'a,  b,c, d'.split(/, */)
// [ 'a', 'b', 'c', 'd' ]// 指定返回数组的最大成员
'a,  b,c, d'.split(/, */, 2)
[ 'a', 'b' ]

27 JSON

上面代码将各种类型的值,转成 JSON 字符串。

注意,对于原始类型的字符串,转换结果会带双引号。

JSON.stringify('foo') === "foo" // false
JSON.stringify('foo') === "\"foo\"" // true

上面代码中,字符串foo,被转成了"“foo”"。这是因为将来还原的时候,内层双引号可以让 JavaScript 引擎知道,这是一个字符串,而不是其他类型的值。

JSON.stringify(false) // "false"
JSON.stringify('false') // "\"false\""

上面代码中,如果不是内层的双引号,将来还原的时候,引擎就无法知道原始值是布尔值还是字符串。

如果对象的属性是undefined、函数或 XML 对象,该属性会被JSON.stringify过滤。

var obj = {a: undefined,b: function () {}
};JSON.stringify(obj) // "{}"

上面代码中,对象obj的a属性是undefined,而b属性是一个函数,结果都被JSON.stringify过滤。

如果数组的成员是undefined、函数或 XML 对象,则这些值被转成null。

var arr = [undefined, function () {}];
JSON.stringify(arr) // "[null,null]"

JSON.stringify方法会忽略对象的不可遍历的属性。

JSON.stringify方法还可以接受一个数组,作为第二个参数,指定需要转成字符串的属性。

var obj = {'prop1': 'value1','prop2': 'value2','prop3': 'value3'
};var selectedProperties = ['prop1', 'prop2'];JSON.stringify(obj, selectedProperties)
// "{"prop1":"value1","prop2":"value2"}"

上面代码中,JSON.stringify方法的第二个参数指定,只转prop1和prop2两个属性。

这个类似白名单的数组,只对对象的属性有效,对数组无效。

JSON.stringify(['a', 'b'], ['0'])
// "["a","b"]"JSON.stringify({0: 'a', 1: 'b'}, ['0'])

第二个参数还可以是一个函数,用来更改JSON.stringify的返回值。

function f(key, value) {if (typeof value === "number") {value = 2 * value;}return value;
}JSON.stringify({ a: 1, b: 2 }, f)
// '{"a": 2,"b": 4}'

上面代码中的f函数,接受两个参数,分别是被转换的对象的键名和键值。如果键值是数值,就将它乘以2,否则就原样返回。

注意,这个处理函数是递归处理所有的键。
递归处理中,每一次处理的对象,都是前一次返回的值。

var o = {a: 1};function f(key, value) {if (typeof value === 'object') {return {b: 2};}return value * 2;
}JSON.stringify(o, f)
// "{"b": 4}"

第一次键名为空,键值是整个对象o;第二次键名为a,键值为1。

JSON.stringify还可以接受第三个参数,用于增加返回的 JSON 字符串的可读性。如果是数字,表示每个属性前面添加的空格(最多不超过10个);如果是字符串(不超过10个字符),则该字符串会添加在每行前面。

JSON.stringify({ p1: 1, p2: 2 }, null, 2);
/*
"{"p1": 1,"p2": 2
}"
*/JSON.stringify({ p1:1, p2:2 }, null, '|-');
/*
"{
|-"p1": 1,
|-"p2": 2
}"
*/

如果传入的字符串不是有效的 JSON 格式,JSON.parse方法将报错。

JSON.parse("'String'") // illegal single quotes
// SyntaxError: Unexpected token ILLEGAL

上面代码中,双引号字符串中是一个单引号字符串,因为单引号字符串不符合 JSON 格式,所以报错。

为了处理解析错误,可以将JSON.parse方法放在try…catch代码块中。

try {JSON.parse("'String'");
} catch(e) {console.log('parsing error');
}

JSON.parse方法可以接受一个处理函数,作为第二个参数,用法与JSON.stringify方法类似。

function f(key, value) {if (key === 'a') {return value + 10;}return value;
}JSON.parse('{"a": 1, "b": 2}', f)

28 object

另一个解决办法,构造函数内部判断是否使用new命令,如果发现没有使用,则直接返回一个实例对象。

function Fubar(foo, bar) {if (!(this instanceof Fubar)) {return new Fubar(foo, bar);}this._foo = foo;this._bar = bar;
}Fubar(1, 2)._foo // 1
(new Fubar(1, 2))._foo // 1

new 命令的原理
使用new命令时,它后面的函数依次执行下面的步骤。

创建一个空对象,作为将要返回的对象实例。
将这个空对象的原型,指向构造函数的prototype属性。
将这个空对象赋值给函数内部的this关键字。
开始执行构造函数内部的代码。
也就是说,构造函数内部,this指的是一个新生成的空对象,所有针对this的操作,都会发生在这个空对象上。构造函数之所以叫“构造函数”,就是说这个函数的目的,就是操作一个空对象(即this对象),将其“构造”为需要的样子。

如果构造函数内部有return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象;否则,就会不管return语句,返回this对象。

var Vehicle = function () {this.price = 1000;return 1000;
};(new Vehicle()) === 1000
// false

上面代码中,构造函数Vehicle的return语句返回一个数值。这时,new命令就会忽略这个return语句,返回“构造”后的this对象。

但是,如果return语句返回的是一个跟this无关的新对象,new命令会返回这个新对象,而不是this对象。这一点需要特别引起注意。

var Vehicle = function (){this.price = 1000;return { price: 2000 };
};(new Vehicle()).price
// 2000

上面代码中,构造函数Vehicle的return语句,返回的是一个新对象。new命令会返回这个对象,而不是this对象。

另一方面,如果对普通函数(内部没有this关键字的函数)使用new命令,则会返回一个空对象。

function getMessage() {return 'this is a message';
}var msg = new getMessage();msg // {}
typeof msg // "object"

上面代码中,getMessage是一个普通函数,返回一个字符串。对它使用new命令,会得到一个空对象。这是因为new命令总是返回一个对象,要么是实例对象,要么是return语句指定的对象。本例中,return语句返回的是字符串,所以new命令就忽略了该语句。

new命令简化的内部流程,可以用下面的代码表示。

function _new(/* 构造函数 */ constructor, /* 构造函数参数 */ params) {// 将 arguments 对象转为数组var args = [].slice.call(arguments);// 取出构造函数var constructor = args.shift();// 创建一个空对象,继承构造函数的 prototype 属性var context = Object.create(constructor.prototype);// 执行构造函数var result = constructor.apply(context, args);// 如果返回结果是对象,就直接返回,否则返回 context 对象return (typeof result === 'object' && result != null) ? result : context;
}// 实例
var actor = _new(Person, '张三', 28);

函数体里面的this.x就是指当前运行环境的x。

var f = function () {console.log(this.x);
}var x = 1;
var obj = {f: f,x: 2,
};// 单独执行
f() // 1// obj 环境执行
obj.f() // 2

上面代码中,函数f在全局环境执行,this.x指向全局环境的x;在obj环境执行,this.x指向obj.x。

对象的方法

如果对象的方法里面包含this,this的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this的指向。

但是,这条规则很不容易把握。请看下面的代码。

var obj ={foo: function () {console.log(this);}
};obj.foo() // obj

上面代码中,obj.foo方法执行时,它内部的this指向obj。

但是,下面这几种用法,都会改变this的指向。

// 情况一
(obj.foo = obj.foo)() // window
// 情况二
(false || obj.foo)() // window
// 情况三
(1, obj.foo)() // window

上面代码中,obj.foo就是一个值。这个值真正调用的时候,运行环境已经不是obj了,而是全局环境,所以this不再指向obj。

可以这样理解,JavaScript 引擎内部,obj和obj.foo储存在两个内存地址,称为地址一和地址二。obj.foo()这样调用时,是从地址一调用地址二,因此地址二的运行环境是地址一,this指向obj。但是,上面三种情况,都是直接取出地址二进行调用,这样的话,运行环境就是全局环境,因此this指向全局环境。上面三种情况等同于下面的代码。

// 情况一
(obj.foo = function () {console.log(this);
})()
// 等同于
(function () {console.log(this);
})()// 情况二
(false || function () {console.log(this);
})()// 情况三
(1, function () {console.log(this);
})()

如果this所在的方法不在对象的第一层,这时this只是指向当前一层的对象,而不会继承更上面的层。

var a = {p: 'Hello',b: {m: function() {console.log(this.p);}}
};a.b.m() // undefined

上面代码中,a.b.m方法在a对象的第二层,该方法内部的this不是指向a,而是指向a.b,因为实际执行的是下面的代码。

var b = {m: function() {console.log(this.p);}
};var a = {p: 'Hello',b: b
};(a.b).m() // 等同于 b.m()

如果要达到预期效果,只有写成下面这样。

var a = {b: {m: function() {console.log(this.p);},p: 'Hello'}
};

如果这时将嵌套对象内部的方法赋值给一个变量,this依然会指向全局对象。

var a = {b: {m: function() {console.log(this.p);},p: 'Hello'}
};var hello = a.b.m;
hello() // undefined

上面代码中,m是多层对象内部的一个方法。为求简便,将其赋值给hello变量,结果调用时,this指向了顶层对象。为了避免这个问题,可以只将m所在的对象赋值给hello,这样调用时,this的指向就不会变。

var hello = a.b;
hello.m() // Hello

数组的map和foreach方法,允许提供一个函数作为参数。这个函数内部不应该使用this。

var o = {v: 'hello',p: [ 'a1', 'a2' ],f: function f() {this.p.forEach(function (item) {console.log(this.v + ' ' + item);});}
}o.f()
// undefined a1
// undefined a2

上面代码中,foreach方法的回调函数中的this,其实是指向window对象,因此取不到o.v的值。原因跟上一段的多层this是一样的,就是内层的this不指向外部,而指向顶层对象。

解决这个问题的一种方法,就是前面提到的,使用中间变量固定this。

var o = {v: 'hello',p: [ 'a1', 'a2' ],f: function f() {var that = this;this.p.forEach(function (item) {console.log(that.v+' '+item);});}
}o.f()
// hello a1
// hello a2

另一种方法是将this当作foreach方法的第二个参数,固定它的运行环境。

var o = {v: 'hello',p: [ 'a1', 'a2' ],f: function f() {this.p.forEach(function (item) {console.log(this.v + ' ' + item);}, this);}
}o.f()
// hello a1
// hello a2

this的动态切换,固然为 JavaScript 创造了巨大的灵活性,但也使得编程变得困难和模糊。有时,需要把this固定下来,避免出现意想不到的情况。JavaScript 提供了call、apply、bind这三个方法,来切换/固定this的指向。

Function.prototype.call()
函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。

var obj = {};var f = function () {return this;
};f() === window // true
f.call(obj) === obj // true

上面代码中,全局环境运行函数f时,this指向全局环境(浏览器为window对象);call方法可以改变this的指向,指定this指向对象obj,然后在对象obj的作用域中运行函数f

call方法还可以接受多个参数。

func.call(thisValue, arg1, arg2, ...)

call的第一个参数就是this所要指向的那个对象,后面的参数则是函数调用时所需的参数。

function add(a, b) {return a + b;
}add.call(this, 1, 2) // 3

Function.prototype.apply()
apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下。

func.apply(thisValue, [arg1, arg2, ...])
function f(x, y){console.log(x + y);
}f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2

上面代码中,f函数本来接受两个参数,使用apply方法以后,就变成可以接受一个数组作为参数。

利用这一点,可以做一些有趣的应用。

(1)找出数组最大元素

JavaScript 不提供找出数组最大元素的函数。结合使用apply方法和Math.max方法,就可以返回数组的最大元素。

var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a) // 15

(2)将数组的空元素变为undefined

通过apply方法,利用Array构造函数将数组的空元素变成undefined。

Array.apply(null, ['a', ,'b'])
// [ 'a', undefined, 'b' ]

空元素与undefined的差别在于,数组的forEach方法会跳过空元素,但是不会跳过undefined。因此,遍历内部元素的时候,会得到不同的结果。

var a = ['a', , 'b'];function print(i) {console.log(i);
}a.forEach(print)
// a
// bArray.apply(null, a).forEach(print)
// a
// undefined
// b

(3)转换类似数组的对象

另外,利用数组对象的slice方法,可以将一个类似数组的对象(比如arguments对象)转为真正的数组。

Array.prototype.slice.apply({0: 1, length: 1}) // [1]
Array.prototype.slice.apply({0: 1}) // []
Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
Array.prototype.slice.apply({length: 1}) // [undefined]

上面代码的apply方法的参数都是对象,但是返回结果都是数组,这就起到了将对象转成数组的目的。从上面代码可以看到,这个方法起作用的前提是,被处理的对象必须有length属性,以及相对应的数字键。

(4)绑定回调函数的对象

前面的按钮点击事件的例子,可以改写如下。

var o = new Object();o.f = function () {console.log(this === o);
}var f = function (){o.f.apply(o);// 或者 o.f.call(o);
};// jQuery 的写法
$('#button').on('click', f);

bind还可以接受更多的参数,将这些参数绑定原函数的参数。

var add = function (x, y) {return x * this.m + y * this.n;
}var obj = {m: 2,n: 2
};var newAdd = add.bind(obj, 5);
newAdd(5) // 20

上面代码中,bind方法除了绑定this对象,还将add函数的第一个参数x绑定成5,然后返回一个新函数newAdd,这个函数只要再接受一个参数y就能运行了。

如果bind方法的第一个参数是null或undefined,等于将this绑定到全局对象,函数运行时this指向顶层对象(浏览器为window)。

function add(x, y) {return x + y;
}var plus5 = add.bind(null, 5);
plus5(10) // 15

上面代码中,函数add内部并没有this,使用bind方法的主要目的是绑定参数x,以后每次运行新函数plus5,就只需要提供另一个参数y就够了。而且因为add内部没有this,所以bind的第一个参数是null,不过这里如果是其他对象,也没有影响。

bind方法有一些使用注意点。

(1)每一次返回一个新函数

bind方法每运行一次,就返回一个新函数,这会产生一些问题。比如,监听事件的时候,不能写成下面这样。

element.addEventListener('click', o.m.bind(o));

上面代码中,click事件绑定bind方法生成的一个匿名函数。这样会导致无法取消绑定,所以,下面的代码是无效的。

element.removeEventListener('click', o.m.bind(o));

正确的方法是写成下面这样:

var listener = o.m.bind(o);
element.addEventListener('click', listener);
//  ...
element.removeEventListener('click', listener);

(2)结合回调函数使用

回调函数是 JavaScript 最常用的模式之一,但是一个常见的错误是,将包含this的方法直接当作回调函数。解决方法就是使用bind方法,将counter.inc绑定counter。

var counter = {count: 0,inc: function () {'use strict';this.count++;}
};function callIt(callback) {callback();
}callIt(counter.inc.bind(counter));
counter.count // 1

上面代码中,callIt方法会调用回调函数。这时如果直接把counter.inc传入,调用时counter.inc内部的this就会指向全局对象。使用bind方法将counter.inc绑定counter以后,就不会有这个问题,this总是指向counter。

还有一种情况比较隐蔽,就是某些数组方法可以接受一个函数当作参数。这些函数内部的this指向,很可能也会出错。

var obj = {name: '张三',times: [1, 2, 3],print: function () {this.times.forEach(function (n) {console.log(this.name);});}
};obj.print()
// 没有任何输出

上面代码中,obj.print内部this.times的this是指向obj的,这个没有问题。但是,forEach方法的回调函数内部的this.name却是指向全局对象,导致没有办法取到值。稍微改动一下,就可以看得更清楚。

obj.print = function () {this.times.forEach(function (n) {console.log(this === window);});
};obj.print()
// true
// true
// true

解决这个问题,也是通过bind方法绑定this。

obj.print = function () {this.times.forEach(function (n) {console.log(this.name);}.bind(this));
};obj.print()
// 张三
// 张三
// 张三

(3)结合call方法使用

利用bind方法,可以改写一些 JavaScript 原生方法的使用形式,以数组的slice方法为例。

[1, 2, 3].slice(0, 1) // [1]
// 等同于
Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]

上面的代码中,数组的slice方法从[1, 2, 3]里面,按照指定位置和长度切分出另一个数组。这样做的本质是在[1, 2, 3]上面调用Array.prototype.slice方法,因此可以用call方法表达这个过程,得到同样的结果。

call方法实质上是调用Function.prototype.call方法,因此上面的表达式可以用bind方法改写。


var slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1, 2, 3], 0, 1) // [1]
上面代码的含义就是,将Array.prototype.slice变成Function.prototype.call方法所在的对象,调用时就变成了Array.prototype.slice.call。类似的写法还可以用于其他数组方法。

var push = Function.prototype.call.bind(Array.prototype.push);
var pop = Function.prototype.call.bind(Array.prototype.pop);

var a = [1 ,2 ,3];
push(a, 4)
a // [1, 2, 3, 4]

pop(a)
a // [1, 2, 3]
如果再进一步,将Function.prototype.call方法绑定到Function.prototype.bind对象,就意味着bind的调用形式也可以被改写。

function f() {
console.log(this.v);
}

var o = { v: 123 };
var bind = Function.prototype.call.bind(Function.prototype.bind);
bind(f, o)() // 123
上面代码的含义就是,将Function.prototype.bind方法绑定在Function.prototype.call上面,所以bind方法就可以直接使用,不需要在函数实例上使用。

29 对象继承

如果让构造函数的prototype属性指向一个数组,就意味着实例对象可以调用数组方法。

var MyArray = function () {};MyArray.prototype = new Array();
MyArray.prototype.constructor = MyArray;var mine = new MyArray();
mine.push(1, 2, 3);
mine.length // 3
mine instanceof Array // true

上面代码中,mine是构造函数MyArray的实例对象,由于MyArray.prototype指向一个数组实例,使得mine可以调用数组方法(这些方法定义在数组实例的prototype对象上面)。最后那行instanceof表达式,用来比较一个对象是否为某个构造函数的实例,结果就是证明mine为Array的实例,instanceof运算符的详细解释详见后文。

constructor 属性 #
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。

function P() {}
P.prototype.constructor === P // true

所以,修改原型对象时,一般要同时修改constructor属性的指向。

// 坏的写法
C.prototype = {method1: function (...) { ... },// ...
};// 好的写法
C.prototype = {constructor: C,method1: function (...) { ... },// ...
};// 更好的写法
C.prototype.method1 = function (...) { ... };

上面代码中,要么将constructor属性重新指向原来的构造函数,要么只在原型对象上添加方法,这样可以保证instanceof运算符不会失真。

如果不能确定constructor属性是什么函数,还有一个办法:通过name属性,从实例得到构造函数的名称。

function Foo() {}
var f = new Foo();
f.constructor.name // "Foo"

由于任意对象(除了null)都是Object的实例,所以instanceof运算符可以判断一个值是否为非null的对象。


var obj = { foo: 123 };
obj instanceof Object // true
null instanceof Object // false

上面代码中,除了null,其他对象的instanceOf Object的运算结果都是true。

instanceof的原理是检查右边构造函数的prototype属性,是否在左边对象的原型链上。有一种特殊情况,就是左边对象的原型链上,只有null对象。这时,instanceof判断会失真。

var obj = Object.create(null);
typeof obj // "object"
Object.create(null) instanceof Object // false

上面代码中,Object.create(null)返回一个新对象obj,它的原型是null(Object.create的详细介绍见后文)。右边的构造函数Object的prototype属性,不在左边的原型链上,因此instanceof就认为obj不是Object的实例。但是,只要一个对象的原型不是null,instanceof运算符的判断就不会失真。

instanceof运算符的一个用处,是判断值的类型。

var x = [1, 2, 3];
var y = {};
x instanceof Array // true
y instanceof Object // true

上面代码中,instanceof运算符判断,变量x是数组,变量y是对象。

注意,instanceof运算符只能用于对象,不适用原始类型的值。

var s = 'hello';
s instanceof String // false

上面代码中,字符串不是String对象的实例(因为字符串不是对象),所以返回false。

此外,对于undefined和null,instanceof运算符总是返回false。

undefined instanceof Object // false
null instanceof Object // false

利用instanceof运算符,还可以巧妙地解决,调用构造函数时,忘了加new命令的问题。

function Fubar (foo, bar) {if (this instanceof Fubar) {this._foo = foo;this._bar = bar;} else {return new Fubar(foo, bar);}
}

让一个构造函数继承另一个构造函数,是非常常见的需求。这可以分成两步实现。第一步是在子类的构造函数中,调用父类的构造函数。

function Sub(value) {
Super.call(this)
this.prop = value
}

上面代码中,Sub是子类的构造函数,this是子类的实例。在实例上调用父类的构造函数Super,就会让子类实例具有父类实例的属性。

第二步,是让子类的原型指向父类的原型,这样子类就可以继承父类原型。

Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.method = '...';

上面代码中,Sub.prototype是子类的原型,要将它赋值为Object.create(Super.prototype),而不是直接等于Super.prototype。否则后面两行对Sub.prototype的操作,会连父类的原型Super.prototype一起修改掉。

另外一种写法是Sub.prototype等于一个父类实例。

Sub.prototype = new Super();

上面这种写法也有继承的效果,但是子类会具有父类实例的方法。有时,这可能不是我们需要的,所以不推荐使用这种写法。

举例来说,下面是一个Shape构造函数。

function Shape() {this.x = 0;this.y = 0;
}Shape.prototype.move = function (x, y) {this.x += x;this.y += y;console.info('Shape moved.');
};

我们需要让Rectangle构造函数继承Shape。

// 第一步,子类继承父类的实例
function Rectangle() {Shape.call(this); // 调用父类构造函数
}
// 另一种写法
function Rectangle() {this.base = Shape;this.base();
}// 第二步,子类继承父类的原型
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

采用这样的写法以后,instanceof运算符会对子类和父类的构造函数,都返回true。

var rect = new Rectangle();rect instanceof Rectangle  // true
rect instanceof Shape  // true

上面代码中,子类是整体继承父类。有时只需要单个方法的继承,这时可以采用下面的写法。

ClassB.prototype.print = function() {
ClassA.prototype.print.call(this);
// some code
}
上面代码中,子类B的print方法先调用父类A的print方法,再部署自己的代码。这就等于继承了父类A的print方法。

模块是实现特定功能的一组属性和方法的封装。

简单的做法是把模块写成一个对象,所有的模块成员都放到这个对象里面。

var module1 = new Object({_count : 0,m1 : function (){//...},m2 : function (){//...}
});

上面的函数m1和m2,都封装在module1对象里。使用的时候,就是调用这个对象的属性。

module1.m1();

但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。

module1._count = 5;

封装私有变量:构造函数的写法
我们可以利用构造函数,封装私有变量。

function StringBuilder() {var buffer = [];this.add = function (str) {buffer.push(str);};this.toString = function () {return buffer.join('');};}

上面代码中,buffer是模块的私有变量。一旦生成实例对象,外部是无法直接访问buffer的。但是,这种方法将私有变量封装在构造函数中,导致构造函数与实例对象是一体的,总是存在于内存之中,无法在使用完成后清除。这意味着,构造函数有双重作用,既用来塑造实例对象,又用来保存实例对象的数据,违背了构造函数与实例对象在数据上相分离的原则(即实例对象的数据,不应该保存在实例对象以外)。同时,非常耗费内存。

function StringBuilder() {this._buffer = [];
}StringBuilder.prototype = {constructor: StringBuilder,add: function (str) {this._buffer.push(str);},toString: function () {return this._buffer.join('');}
};

这种方法将私有变量放入实例对象中,好处是看上去更自然,但是它的私有变量可以从外部读写,不是很安全。

封装私有变量:立即执行函数的写法
另一种做法是使用“立即执行函数”(Immediately-Invoked Function Expression,IIFE),将相关的属性和方法封装在一个函数作用域里面,可以达到不暴露私有成员的目的。

var module1 = (function () {var _count = 0;var m1 = function () {//...};var m2 = function () {//...};return {m1 : m1,m2 : m2};
})();

使用上面的写法,外部代码无法读取内部的_count变量。

console.info(module1._count); //undefined

上面的module1就是 JavaScript 模块的基本写法。下面,再对这种写法进行加工。

模块的放大模式
如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用“放大模式”
(augmentation)。

var module1 = (function (mod){mod.m3 = function () {//...};return mod;
})(module1);

上面的代码为module1模块添加了一个新方法m3(),然后返回新的module1模块。

在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上面的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用"宽放大模式"(Loose augmentation)。

var module1 = (function (mod) {//...return mod;
})(window.module1 || {});

与"放大模式"相比,“宽放大模式”就是“立即执行函数”的参数可以是空对象。

输入全局变量
独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。

为了在模块内部调用全局变量,必须显式地将其他变量输入模块。

var module1 = (function ($, YAHOO) {//...
})(jQuery, YAHOO);

上面的module1模块需要使用 jQuery 库和 YUI 库,就把这两个库(其实是两个模块)当作参数输入module1。这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。

立即执行函数还可以起到命名空间的作用。

(function($, window, document) {function go(num) {}function handleEvents() {}function initialize() {}function dieCarouselDie() {}//attach to the global scopewindow.finalCarousel = {init : initialize,destroy : dieCarouselDie}})( jQuery, window, document );

上面代码中,finalCarousel对象输出到全局,对外暴露init和destroy接口,内部方法go、handleEvents、initialize、dieCarouselDie都是外部无法调用的。

30 Object 对象的相关方法

// 空对象的原型是 Object.prototype
Object.getPrototypeOf({}) === Object.prototype // true// Object.prototype 的原型是 null
Object.getPrototypeOf(Object.prototype) === null // true// 函数的原型是 Function.prototype
function f() {}
Object.getPrototypeOf(f) === Function.prototype // true

Object.setPrototypeOf方法为参数对象设置原型,返回该参数对象。它接受两个参数,第一个是现有对象,第二个是原型对象。

var a = {};
var b = {x: 1};
Object.setPrototypeOf(a, b);Object.getPrototypeOf(a) === b // true
a.x // 1

上面代码中,Object.setPrototypeOf方法将对象a的原型,设置为对象b,因此a可以共享b的属性。

new命令可以使用Object.setPrototypeOf方法模拟。

var F = function () {this.foo = 'bar';
};var f = new F();
// 等同于
var f = Object.setPrototypeOf({}, F.prototype);
F.call(f);

上面代码中,new命令新建实例对象,其实可以分成两步。第一步,将一个空对象的原型设为构造函数的prototype属性(上例是F.prototype);第二步,将构造函数内部的this绑定这个空对象,然后执行构造函

Object.create()
生成实例对象的常用方法是,使用new命令让构造函数返回一个实例。但是很多时候,只能拿到一个实例对象,它可能根本不是由构建函数生成的,那么能不能从一个实例对象,生成另一个实例对象呢?

JavaScript 提供了Object.create方法,用来满足这种需求。该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象。该实例完全继承原型对象的属性。

// 原型对象
var A = {print: function () {console.log('hello');}
};// 实例对象
var B = Object.create(A);Object.getPrototypeOf(B) === A // true
B.print() // hello
B.print === A.print // true

上面代码中,Object.create方法以A对象为原型,生成了B对象。B继承了A的所有属性和方法。

实际上,Object.create方法可以用下面的代码代替。

if (typeof Object.create !== 'function') {Object.create = function (obj) {function F() {}F.prototype = obj;return new F();};
}

上面代码表明,Object.create方法的实质是新建一个空的构造函数F,然后让F.prototype属性指向参数对象obj,最后返回一个F的实例,从而实现让该实例继承obj的属性。

下面三种方式生成的新对象是等价的。

var obj1 = Object.create({});
var obj2 = Object.create(Object.prototype);
var obj3 = new Object();

如果想要生成一个不继承任何属性(比如没有toString和valueOf方法)的对象,可以将Object.create的参数设为null。

var obj = Object.create(null);obj.valueOf()
// TypeError: Object [object Object] has no method 'valueOf'

Object.create方法生成的对象,继承了它的原型对象的构造函数。

function A() {}
var a = new A();
var b = Object.create(a);b.constructor === A // true
b instanceof A // true

上面代码中,b对象的原型是a对象,因此继承了a对象的构造函数A。

获取原型对象方法的比较
如前所述,__proto__属性指向当前对象的原型对象,即构造函数的prototype属性。

var obj = new Object();obj.__proto__ === Object.prototype
// true
obj.__proto__ === obj.constructor.prototype
// true

上面代码首先新建了一个对象obj,它的__proto__属性,指向构造函数(Object或obj.constructor)的prototype属性。

因此,获取实例对象obj的原型对象,有三种方法。

obj.proto
obj.constructor.prototype
Object.getPrototypeOf(obj)
上面三种方法之中,前两种都不是很可靠。__proto__属性只有浏览器才需要部署,其他环境可以不部署。而obj.constructor.prototype在手动改变原型对象时,可能会失效。

var P = function () {};
var p = new P();var C = function () {};
C.prototype = p;
var c = new C();c.constructor.prototype === p // false

上面代码中,构造函数C的原型对象被改成了p,但是实例对象的c.constructor.prototype却没有指向p。所以,在改变原型对象时,一般要同时设置constructor属性。

C.prototype = p;
C.prototype.constructor = C;var c = new C();
c.constructor.prototype === p // true

因此,推荐使用第三种Object.getPrototypeOf方法,获取原型对象。

对象的拷贝
如果要拷贝一个对象,需要做到下面两件事情。

确保拷贝后的对象,与原对象具有同样的原型。
确保拷贝后的对象,与原对象具有同样的实例属性。
下面就是根据上面两点,实现的对象拷贝函数。

function copyObject(orig) {var copy = Object.create(Object.getPrototypeOf(orig));copyOwnPropertiesFrom(copy, orig);return copy;
}function copyOwnPropertiesFrom(target, source) {Object.getOwnPropertyNames(source).forEach(function (propKey) {var desc = Object.getOwnPropertyDescriptor(source, propKey);Object.defineProperty(target, propKey, desc);});return target;
}

另一种更简单的写法,是利用 ES2017 才引入标准的Object.getOwnPropertyDescriptors方法。

function copyObject(orig) {return Object.create(Object.getPrototypeOf(orig),Object.getOwnPropertyDescriptors(orig));
}

上面代码想在用户每次输入文本后,立即将字符转为大写。但是实际上,它只能将本次输入前的字符转为大写,因为浏览器此时还没接收到新的文本,所以this.value取不到最新输入的那个字符。只有用setTimeout改写,上面的代码才能发挥作用。

document.getElementById('input-box').onkeypress = function() {var self = this;setTimeout(function() {self.value = self.value.toUpperCase();}, 0);
}

上面代码将代码放入setTimeout之中,就能使得它在浏览器接收到文本之后触发。

由于setTimeout(f, 0)实际上意味着,将任务放到浏览器最早可得的空闲时段执行,所以那些计算量大、耗时长的任务,常常会被放到几个小部分,分别放到setTimeout(f, 0)里面执行。

var div = document.getElementsByTagName('div')[0];// 写法一
for (var i = 0xA00000; i < 0xFFFFFF; i++) {div.style.backgroundColor = '#' + i.toString(16);
}// 写法二
var timer;
var i=0x100000;function func() {timer = setTimeout(func, 0);div.style.backgroundColor = '#' + i.toString(16);if (i++ == 0xFFFFFF) clearTimeout(timer);
}timer = setTimeout(func, 0);

上面代码有两种写法,都是改变一个网页元素的背景色。写法一会造成浏览器“堵塞”,因为 JavaScript 执行速度远高于 DOM,会造成大量 DOM 操作“堆积”,而写法二就不会,这就是setTimeout(f, 0)的好处。

另一个使用这种技巧的例子是代码高亮的处理。如果代码块很大,一次性处理,可能会对性能造成很大的压力,那么将其分成一个个小块,一次处理一块,比如写成setTimeout(highlightNext, 50)的样子,性能压力就会减轻。

31document

document.createEvent() #
document.createEvent方法生成一个事件对象(Event实例),该对象可以被element.dispatchEvent方法使用,触发指定事件。

var event = document.createEvent(type);

document.createEvent方法的参数是事件类型,比如UIEvents、MouseEvents、MutationEvents、HTMLEvents。

var event = document.createEvent('Event');
event.initEvent('build', true, true);
document.addEventListener('build', function (e) {console.log(e.type); // "build"
}, false);
document.dispatchEvent(event);

上面代码新建了一个名为build的事件实例,然后触发该事件。

document.addEventListener(),document.removeEventListener(),document.dispatchEvent()
这三个方法用于处理document节点的事件。它们都继承自EventTarget接口,详细介绍参见《EventTarget 接口》一章。

// 添加事件监听函数
document.addEventListener('click', listener, false);// 移除事件监听函数
document.removeEventListener('click', listener, false);// 触发事件
var event = new Event('click');
document.dispatchEvent(event);

document.queryCommandSupported()

document.queryCommandSupported()方法返回一个布尔值,表示浏览器是否支持document.execCommand()的某个命令。

if (document.queryCommandSupported('SelectAll')) {console.log('浏览器支持选中可编辑区域的所有内容');
}

document.queryCommandEnabled()

document.queryCommandEnabled()方法返回一个布尔值,表示当前是否可用document.execCommand()的某个命令。比如,bold(加粗)命令只有存在文本选中时才可用,如果没有选中文本,就不可用。

// HTML 代码为
// <input type="button" value="Copy" onclick="doCopy()">function doCopy(){// 浏览器是否支持 copy 命令(选中内容复制到剪贴板)if (document.queryCommandSupported('copy')) {copyText('你好');}else{console.log('浏览器不支持');}
}function copyText(text) {var input = document.createElement('textarea');document.body.appendChild(input);input.value = text;input.focus();input.select();// 当前是否有选中文字if (document.queryCommandEnabled('copy')) {var success = document.execCommand('copy');input.remove();console.log('Copy Ok');} else {console.log('queryCommandEnabled is false');}
}

上面代码中,先判断浏览器是否支持copy命令(允许可编辑区域的选中内容,复制到剪贴板),如果支持,就新建一个临时文本框,里面写入内容“你好”,并将其选中。然后,判断是否选中成功,如果成功,就将“你好”复制到剪贴板,再删除那个临时文本框。

Element.getBoundingClientRect() #
Element.getBoundingClientRect方法返回一个对象,提供当前元素节点的大小、位置等信息,基本上就是 CSS 盒状模型的所有信息。

var rect = obj.getBoundingClientRect();

上面代码中,getBoundingClientRect方法返回的rect对象,具有以下属性(全部为只读)。

x:元素左上角相对于视口的横坐标
y:元素左上角相对于视口的纵坐标
height:元素高度
width:元素宽度
left:元素左上角相对于视口的横坐标,与x属性相等
right:元素右边界相对于视口的横坐标(等于x + width)
top:元素顶部相对于视口的纵坐标,与y属性相等
bottom:元素底部相对于视口的纵坐标(等于y + height)
由于元素相对于视口(viewport)的位置,会随着页面滚动变化,因此表示位置的四个属性值,都不是固定不变的。如果想得到绝对位置,可以将left属性加上window.scrollX,top属性加上window.scrollY。

注意,getBoundingClientRect方法的所有属性,都把边框(border属性)算作元素的一部分。也就是说,都是从边框外缘的各个点来计算。因此,width和height包括了元素本身 + padding + border。

另外,上面的这些属性,都是继承自原型的属性,Object.keys会返回一个空数组,这一点也需要注意。

var rect = document.body.getBoundingClientRect();
Object.keys(rect) // []

上面代码中,rect对象没有自身属性,而Object.keys方法只返回对象自身的属性,所以返回了一个空数组。

Element.getClientRects()
Element.getClientRects方法返回一个类似数组的对象,里面是当前元素在页面上形成的所有矩形(所以方法名中的Rect用的是复数)。每个矩形都有bottom、height、left、right、top和width六个属性,表示它们相对于视口的四个坐标,以及本身的高度和宽度。

对于盒状元素(比如

Hello World Hello World Hello World
上面代码是一个行内元素,如果它在页面上占据三行,getClientRects方法返回的对象就有三个成员,如果它在页面上占据一行,getClientRects方法返回的对象就只有一个成员。

var el = document.getElementById('inline');
el.getClientRects().length // 3
el.getClientRects()[0].left // 8
el.getClientRects()[0].right // 113.908203125
el.getClientRects()[0].bottom // 31.200000762939453
el.getClientRects()[0].height // 23.200000762939453
el.getClientRects()[0].width // 105.908203125

这个方法主要用于判断行内元素是否换行,以及行内

31 事件

如果希望事件到某个节点为止,不再传播,可以使用事件对象的stopPropagation方法。

// 事件传播到 p 元素后,就不再向下传播了
p.addEventListener('click', function (event) {event.stopPropagation();
}, true);// 事件冒泡到 p 元素后,就不再向上冒泡了
p.addEventListener('click', function (event) {event.stopPropagation();
}, false);

上面代码中,stopPropagation方法分别在捕获阶段和冒泡阶段,阻止了事件的传播。

但是,stopPropagation方法只会阻止事件的传播,不会阻止该事件触发

节点的其他click事件的监听函数。也就是说,不是彻底取消click事件。

p.addEventListener('click', function (event) {event.stopPropagation();console.log(1);
});p.addEventListener('click', function(event) {// 会触发console.log(2);
});

上面代码中,p元素绑定了两个click事件的监听函数。stopPropagation方法只能阻止这个事件的传播,不能取消这个事件,因此,第二个监听函数会触发。输出结果会先是1,然后是2。

如果想要彻底取消该事件,不再触发后面所有click的监听函数,可以使用stopImmediatePropagation方法。


p.addEventListener('click', function (event) {event.stopImmediatePropagation();console.log(1);
});p.addEventListener('click', function(event) {// 不会被触发console.log(2);
});

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。

var ul = document.querySelector('ul');ul.addEventListener('click', function (event) {if (event.target.tagName.toLowerCase() === 'li') {// some code}
});

32 Worker

然后,读取这一段嵌入页面的脚本,用 Worker 来处理。

var blob = new Blob([document.querySelector('#worker').textContent]);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);worker.onmessage = function (e) {// e.data === 'some message'
};

上面代码中,先将嵌入网页的脚本代码,转成一个二进制对象,然后为这个二进制对象生成 URL,再让 Worker 加载这个 URL。这样就做到了,主线程和 Worker 的代码都在同一个网页上面。

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. linux 下 压缩文件

    一、zip格式zip可能是目前使用的最多的文档压缩格式。它最大的优点就是在不同的操作系统平台上使用。缺点就是支持的压缩率不是很高,而tar.gz和tar.bz2在压缩率方面做得非常好。我们可以使用下列的命令压缩一个文件:zip -r archive_name.zip filename (-r是压缩文件)下面…...

    2024/5/2 13:21:25
  2. 基于centos7打造个人服务器(前言)

    写在前面 从入IT圈到现在已经是第五个年头了,一路走来,从测试到开发陆陆续学到了不少干货知识,从系统到软件,从软件到硬件。一直以来都想好好总结下,因此打算以centos7为基础打造一个个人服务器,同时回顾总结下一直以来踩过的坑。 架构图安装清单基于centos7打造个人服务…...

    2024/3/29 6:55:38
  3. springboot -- JPA简单运用

    1、导入相关依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifa…...

    2024/5/2 17:03:47
  4. 实例分割——转置卷积的学习笔记

    2 备注2.1 转置卷积stride的参数如何设置?一种已知的设置方法是设置成2N,其中N为上采样的放大倍数,这样的原因是为了使用双线性插值,即“用四个特征点插值一个下采样点”;不太清楚这种设置方法是否好不好,可以看看Detectron2的设置;...

    2024/3/29 6:55:36
  5. 了解JS

    了解JS1.什么是JavaScript ? 答: 1、JavaScript是一种具有面向对象能力的解释型语言。2、更具体一点,它是基于对象和事件驱动并具有相对安全性的客户端脚本语言。 面向对象: 是编程思维的一种, 我们初期接触的是 面向过程。 2.JavaScript特点 ? 答: (1)松散型 (2)对象…...

    2024/3/29 15:09:58
  6. Leetcode383. 赎金信

    Leetcode383. 赎金信 题目: 给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成。如果可以构成,返回 true ;否则返回 false。 (题目说明:为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母…...

    2024/5/2 14:51:48
  7. Servlet 详解

    https://www.cnblogs.com/whgk/p/6399262.html...

    2024/5/2 15:17:48
  8. spring浅析

    1、spring启动流程web容器为web应用提供一个全局的上下文环境ServletContext,为IOC容器提供宿主环境。web.xml中有contextLoaderListener,在web容器启动时触发容器初始化事件(ApplicationContext可以理解为IOC容器,具体实现有多个,如XmlWebApplicationContext),加载所有…...

    2024/3/29 15:09:56
  9. Spring : @Bean注解

    大数据-flinkflink源码专栏九师兄9.90去订阅1.美图2.概述 @Bean是一个方法级别上的注解,主要用在@SpringBootConfiguration、@Configuration注解的配置类里面使用。Spring Boot会帮助把@Bean注解方法的对象添加到Spring IOC容器里面去,供我们在别的地方使用。 3.源码 @Target…...

    2024/3/29 15:09:54
  10. lnmp

    nginx 相关命令: # {start|stop|restart|reload|status|configtest|force-quit|kill} sudo service nginx start #启动 service nginx stop #停止 service nginx restart #重启 service nginx status #查看状态mysql sudo service mysql restart #重启service mysql status #…...

    2024/4/1 11:39:16
  11. oracle删除指定用户及数据库

    oracle删除指定用户,oracle删除指定数据库oracle查询数据库版本select * from v$version解锁当前用户连接状态alter user FASTMAPLETR80_FAW account lock;commit;删除指定用户 drop user FASTMAPLETR80_FAW cascade;commit;其他方法:重启电脑...

    2024/5/2 8:45:12
  12. idea+jetty启动报错:java.net.BindException: Address already in use: bind

    一.错误信息 2020-01-19 17:01:09.044:INFO:oejs.Server:main: jetty-9.2.1.v20140609 2020-01-19 17:01:09.093:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:/D:/JavaRuanJian/jetty-distribution-9.2.1.v20140609/webapps/] at interval 1 2020-01-19 17…...

    2024/3/29 15:09:51
  13. CSS属性速查表

    本文将按照布局类属性、盒模型属性、文本类属性、修饰类属性这四个分类,对CSS常用属性进行重新排列,并最终设置为一份stylelintrc文件 布局类 1、定位 position z-index top bottom left right2、浮动 float clear3、多列布局 columns columns-width columns-count column-ru…...

    2024/4/16 23:21:34
  14. 深入理解CSS伪元素

    定义 伪元素顾名思义伪装成元素,但不是元素,这与生成内容相关。生成内容主要指由浏览器创建的内容,而不是由标志或内容来表示。生成内容主要由:before和:after伪元素来实现,当然伪元素还包括:first-line,:first-letter和::selection 用法 :first-letter 指定一个元素第一个…...

    2024/3/29 15:09:49
  15. 深入理解CSS伪类

    伪类经常与伪元素混淆,[伪元素]的效果类似于通过添加一个实际的元素才能达到,而伪类的效果类似于通过添加一个实际的类来达到。实际上css3为了区分两者,已经明确规定了伪类用一个冒号来表示,而伪元素则用两个冒号来表示。本文将详细介绍伪类的详细知识 锚点 关于锚点<a&…...

    2024/3/29 15:09:48
  16. 【信息技术】【2009】基于特征的图像配准

    本文为印度Rourkela国立技术研究所(作者:SOMARAJU.BODA)的硕士论文,共71页。 图像配准是用于匹配两个或多个部分重叠图像的基本任务,例如,在不同的时间、从不同的传感器或从不同的视角拍摄的图像,将这些图像整合成包含整个场景的全景图像。它是一种基本的图像处理技术,…...

    2024/3/29 6:55:51
  17. 深入理解CSS计数器

    我们对计数器已经不陌生了,有序列表中的列表项标志就是计数器。 创建计数器创建计数器的基础包括两个方面,一是能重置计数器的起点,二是能将其递增一定的量。 counter-reset counter-reset:none;(默认) counter-reset:<identifier><integer> //<identifier&g…...

    2024/5/2 15:03:14
  18. CSS Hack实现浏览器样式兼容的兜底办法

    CSS Hack是实现浏览器样式兼容的兜底办法,能不用就尽量不要使用。但是,针对一些浏览器的bug,比如老版本IE的bug,有时使用CSS Hack是不得已而为之的做法。本文将详细介绍CSS Hack。CSS Hack主要分为属性标记法和选择器前缀法两种 属性标记法 【IE6-】 对于IE6-浏览器主要使用…...

    2024/4/4 15:13:25
  19. AndroidStudio运行App到模拟器

    Adroid项目有时候想运行在真机上查看效果,最好的效果就是运行电脑的Adroid模拟器上了。下面以夜神模拟器为例:在模拟器的安装目录下找到adb 夜神模拟器的adb在如下目录下: 注意:其它模拟器的adb有的名字为其它名字cmd进入adb目录运行如下命令 adb connect 127.0.0.1:62001在…...

    2024/3/29 6:55:48
  20. 轻松解决pip show pkg出错的问题

    在使用pip的时候出现了如下问题:No such file or directory: d:\\anaconda\\lib\\site-packages\\PyHamcrest-1.9.0.dist-info\\METADATA解决方法:pip install PyHamcrest==1.9.0 或者 conda install PyHamcrest==1.9.0那么以此类推,报错说没有什么文件之类的,看一下是不是…...

    2024/4/5 7:00:35

最新文章

  1. 信息时代的智慧导航:高效搜索、信息筛选与信任构建的全面指南!

    文章目录 一、高效搜索&#xff1a;快速定位目标信息的秘诀二、信息筛选&#xff1a;去伪存真&#xff0c;找到有价值的信息三、信任构建&#xff1a;深入了解与直接沟通《搜索之道&#xff1a;信息素养与终身学习的新引擎》亮点内容简介目录获取方式 随着科技的飞速发展&#…...

    2024/5/2 18:49:52
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. ASP.NET Core 标识(Identity)框架系列(一):如何使用 ASP.NET Core 标识(Identity)框架创建用户和角色?

    前言 ASP.NET Core 内置的标识&#xff08;identity&#xff09;框架&#xff0c;采用的是 RBAC&#xff08;role-based access control&#xff0c;基于角色的访问控制&#xff09;策略&#xff0c;是一个用于管理用户身份验证、授权和安全性的框架。 它提供了一套工具和库&…...

    2024/5/1 13:12:26
  4. ssm框架中各层级介绍

    1、Spring&#xff08;业务逻辑层&#xff09;&#xff1a; Spring框架提供了依赖注入&#xff08;DI&#xff09;和面向切面编程&#xff08;AOP&#xff09;等功能&#xff0c;可以帮助管理Java应用程序中的对象依赖关系和提供横切关注点的支持。 在SSM框架中&#xff0c;S…...

    2024/4/30 2:49:54
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/1 17:30:59
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/2 16:16:39
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/2 9:28:15
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/2 15:04:34
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/2 9:07:46
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57