JS中的隐式转换
数学运算符中的类型转换
因为 JS 并没有类型声明,所以任意两个变量或字面量,都可以做加减乘除。
1.减、乘、除
我们在对各种非Number类型运用数学运算符(-*/)时,会先将非Number类型转换为Number类型。
1 - true // 0 首先会把 true转换为数字1,然后执行1-11 - null // 1, 首先把 null 转换为数字 0, 然后执行 1 - 01 * undefined // NaN, undefined 转换为数字是 NaN2 * ['5'] //10 ['5']首先会变成'5',然后再变成数字5
2.加法的特殊性
为什么加法要区别对待?因为JS里 +还可以用来拼接字符串。谨记以下3条:
- 当一侧为String类型,被识别为字符串拼接,并会优先将另一侧转换为字符串类型。
- 当一侧为Number类型,另一侧为原始类型,则将原始类型转换为Number类型。
- 当一侧为Number类型,另一侧为引用类型,将引用类型和Number类型转换成字符串后拼接。
以上3点,优先级从高到低,即3+’abc’会应用规则1,而3 + true 会引用规则2。
123 + '123' //'123123'123 + null // 123123 + true //124123 + {} // '123[object Object]'
逻辑语句中的类型转换
当我们使用if while for 语句时,我们期望表达一个Boolean,所以一定伴随着隐式类型转换。而这里又分为两种情况:
单个变量
如果只有单个变量,会先将变量转换为Boolean值。
只有null undefined ‘ ‘ NaN 0 false 这几个是false, 其他的情况都是true,比如{},[]。
使用==比较中的5条规则
根据==两侧的数据类型,我们总结出5条规则
规则1:NaN和其他任何类型比较永远返回false(包括和它自己)
NaN == NaN // false
规则 2:Boolean 和其他任何类型比较,Boolean 首先被转换为 Number 类型。
true == 1 // truetrue == '2' // false, 先把 true 变成 1,而不是把 '2' 变成 truetrue == ['1'] // true, 先把 true 变成 1, ['1']拆箱成 '1', 再参考规则3true == ['2'] // false, 同上undefined == false // false ,首先 false 变成 0,然后参考规则4null == false // false,同上
规则 3:String和Number比较,先将String转换为Number类型。
123 == '123' // true, '123' 会先变成 123'' == 0 // true, '' 会首先变成 0
规则 4:null == undefined比较结果是true,除此之外,null、undefined和其他任何结果的比较值都为false。
null == undefined // truenull == '' // falsenull == 0 // falsenull == false // falseundefined == '' // falseundefined == 0 // falseundefined == false // false
规则 5:原始类型和引用类型做比较时,引用类型会依照ToPrimitive规则转换为原始类型。
ToPrimitive规则,是引用类型向原始类型转变的规则,它遵循先valueOf后toString的模式期望得到一个原始类型。<br /> 如果还是没法得到一个原始类型,就会抛出 TypeError。
'[object Object]' == {}// true, 对象和字符串比较,对象通过 toString 得到一个基本类型值'1,2,3' == [1, 2, 3]// true, 同上 [1, 2, 3]通过 toString 得到一个基本类型值
附录1:类型转换表

基本引用类型
String
String 是对应字符串的引用类型。
let stringObject = new String(“hello world”);
String对象的方法可以在所有字符串原始值上调用。3个继承的方法valueOf()、toLocaleString()、toString都返回对象的字符串值。
每个String对象都有一个length属性,表示字符串中字符的数量。
let stringValue = “hello world”;
console.log(stringValue.length); // “11” ```javascript //常用方法
// charAt(),charAt()方法返回给定索引位置的字符 let message = “abcde”; console.log(message.charAt(2)); // “c”
// 字符串操作方法
// concat() 用于将一个或多个字符串拼接成一个新字符串。 let stringValue = “hello “; let result = stringValue.concat(“world”); console.log(result); // “hello world” console.log(stringValue); // “hello” / 这个修改后的例子将字符串”world”和”!”追加到了”hello “后面。虽然 concat()方法可以拼接 字符串,但更常用的方式是使用加号操作符(+)。而且多数情况下,对于拼接多个字符串来说,使用加 号更方便。 /
// slice()、substr()和substring() // 这3个方法都返回调用它们的字符串的一个子字符串,而且都接收一个或两个参数 // 第一个参数表示子字符串开始的位置,第二个参数代表子字符串结束的位置。 // slice()和substring(),第二个参数是提取结束的位置(即该位置之前的字符串会被提取出来) // substr() 第二个参数表示返回的子字符串数量。 // 任何情况下,省略第二个参数都意味着提取到字符串末尾。
let stringValue = “hello world”; console.log(stringValue.slice(3)); // “lo world” console.log(stringValue.substring(3)); // “lo world” console.log(stringValue.substr(3)); // “lo world” console.log(stringValue.slice(3, 7)); // “lo w” console.log(stringValue.substring(3,7)); // “lo w” console.log(stringValue.substr(3, 7)); // “lo worl”
// 当某个参数是负值时,3个方法的行为又有不同。比如,slice()方法是将所有负值参数当做字符串长度加上负参数值 // 人话(slice()参数为负从后面开始取) // 而substr()方法将第一个负参数值当做字符串长度加上该值,将第二个负参数值转换为0。 // substring()方法会将所有负参数值都转换为0 let stringValue = “hello world”; console.log(stringValue.slice(-3)); // “rld” console.log(stringValue.substring(-3)); // “hello world” console.log(stringValue.substr(-3)); // “rld” console.log(stringValue.slice(3, -4)); // “lo w” console.log(stringValue.substring(3, -4)); // “hel” console.log(stringValue.substr(3, -4)); // “” (empty string)
// 字符串位置方法 // indexOf()和lastIndexOf()。这两个方法从字符串中搜索传入字符串,并返回位置,若没有找到返回-1
<a name="xcBGo"></a>## 对象<a name="tqafq"></a>### 理解对象<a name="DMjiB"></a>#### 定义对象创建自定义对象的方式是创建Object的一个新实例,然后再给它添加属性和方法,如下列所示```javascriptlet person = new Object();person.name = "CCC";person.age = 29;person.job = "crawler Engineer";person.sayName = function() {console.log(this.name);}
对象字面量写法
let person = {name : 'ccc',age : '29',job : "crawler Engineer",sayName() {console.log(this.name);}}
对象中属性的特征
前面例子中的 person 对象跟前面例子中的 person 对象是等价的,它们的属性和方法都一样。这
些属性都有自己的特征,而这些特征决定了它们在 JavaScript 中的行为
ECMA-262 使用一些内部特性来描述属性的特征。这些特性是由为 JavaScript 实现引擎的规范定义
的。因此,开发者不能在 JavaScript 中直接访问这些特性。为了将某个特性标识为内部特性,规范会用
两个中括号把特性的名称括起来,比如[[Enumerable]]。
属性分两种:数据属性和访问器属性
1.数据属性
数据属性有4个特性描述它们的行为。
- [[Configurable]]
- [[Enumerable]]
- [[Writable]
- [[Value]]
Object.defineProperty()方法。这个方法接收 3 个参数:
要给其添加属性的对象、属性的名称和一个描述符对象。最后一个参数,即描述符对象上的属性可以包
含:configurable、enumerable、writable 和 value,跟相关特性的名称一一对应。根据要修改
的特性,可以设置其中一个或多个值。
let person = {};Object.defineProperty(person, "name", {writable: false,value: "Nicholas"});console.log(person.name); // "Nicholas"person.name = "Greg";console.log(person.name); // "Nicholas"let person = {};Object.defineProperty(person, "name", {configurable: false,value: "Nicholas"});console.log(person.name); // "Nicholas"delete person.name;console.log(person.name); // "Nicholas"/*这个例子把 configurable 设置为 false,意味着这个属性不能从对象上删除。非严格模式下对这个属性调用 delete 没有效果,严格模式下会抛出错误。此外,一个属性被定义为不可配置之后,就不能再变回可配置的了。再次调用 Object.defineProperty()并修改任何非 writable 属性会导致错误:*/let person = {};Object.defineProperty(person, "name", {configurable: false,value: "Nicholas"});// 抛出错误Object.defineProperty(person, "name", {configurable: true,value: "Nicholas"});
2.访问器属性
访问器属性不包含数据值。它们包含一个set和一个get函数,这个两个函数不是必须的
[[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特
性,以及是否可以把它改为数据属性。默认情况下,所有直接定义在对象上的属性的这个特性
都是 true。
[[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对
象上的属性的这个特性都是 true。
[[Get]]:获取函数,在读取属性时调用。默认值为 undefined。
[[Set]]:设置函数,在写入属性时调用。默认值为 undefined。
let book = {year_: 2017,edition: 1};Object.defineProperty(book, "year",{get() {return this.year_;},set(newValue) {if (newValue > 2017) {this.year_ = newValue;this.edition += newValue - 2017;}}})/*获取函数和设置函数不一定都要定义。只定义获取函数意味着属性是只读的,尝试修改属性会被忽略。在严格模式下,尝试写入只定义了获取函数的属性会抛出错误。类似地,只有一个设置函数的属性是不能读取的,非严格模式下读取会返回 undefined,严格模式下会抛出错误*/
定义多个属性
Object.defineProperties()
它接收两个参数:要为之添 加或修改属性的对象和另一个描述符对象,其属性与要添加或修改的属性一一对应。
let book2 = {};Object.defineProperties(book2,{year_: {value:2017},edition: {value: 1},year: {get() {return this.year_;},set(newValue) {if (newValue > 2017) {this.year_ = newValue;this.edition += newValue - 2017;}}}})/*这段代码在 book 对象上定义了两个数据属性 year_和 edition,还有一个访问器属性 year。最终的对象跟上一节示例中的一样。唯一的区别是所有属性都是同时定义的,并且数据属性的configurable、enumerable 和 writable 特性值都是 false。*/
读取属性特征
Object.getOwnPropertyDescriptor()
这个方法接 收两个参数:属性所在的对象和要取得其描述符的属性名。返回值是一个对象,对于访问器属性包含 configurable、enumerable、get 和 set 属性,对于数据属性包含 configurable、enumerable、 writable 和 value 属性。
let descriptor = Object.getOwnPropertyDescriptor(book, "year_");console.log(descriptor.value); // 2017console.log(descriptor.configurable); // falseconsole.log(typeof descriptor.get); // "undefined"let descriptor = Object.getOwnPropertyDescriptor(book, "year");console.log(descriptor.value); // undefinedconsole.log(descriptor.enumerable); // falseconsole.log(typeof descriptor.get); // "function"let book = {};Object.defineProperties(book, {year_: {value: 2017},edition: {value: 1},year: {get: function() {return this.year_;},set: function(newValue){if (newValue > 2017) {this.year_ = newValue;this.edition += newValue - 2017;}}}});console.log(Object.getOwnPropertyDescriptors(book));// {// edition: {// configurable: false,// enumerable: false,// value: 1,// writable: false// },// year: {// configurable: false,// enumerable: false,// get: f(),// set: f(newValue),// },// year_: {// configurable: false,// enumerable: false,// value: 2017,// writable: false// }// }
合并对象
Object.assign()
这个方法接收一个目标对象和一个或多个源对象作为参数,然后将每个源对象中可枚举(Object.propertyIsEnumerable()返回 true) 和自有(Object.hasOwnProperty()返回 true)属性复制到目标对象。以字符串和符号为键的属性会被复制。对每个符合条件的属性,这个方法会使用源对象上的[[Get]]取得属性的值,然后使用目标对象上的[[Set]]设置属性的值。
let dest, src, result;/*** 简单复制*/dest = {};src = { id: 'src' };result = Object.assign(dest, src);// Object.assign 修改目标对象// 也会返回修改后的目标对象console.log(dest === result); // trueconsole.log(dest !== src); // trueconsole.log(result); // { id: src }console.log(dest); // { id: src }/*** 多个源对象*/dest = {};result = Object.assign(dest, { a: 'foo' }, { b: 'bar' });console.log(result); // { a: foo, b: bar }
对象标识及相等判定
Object.is()
console.log(Object.is(true, 1)) // falseconsole.log(Object.is("2", 2)); // false// 正确的 0、-0、+0 相等/不等判定console.log(Object.is(+0, -0)); // falseconsole.log(Object.is(+0, 0)); // trueconsole.log(Object.is(-0, 0)); // false// 正确的 NaN 相等判定console.log(Object.is(NaN, NaN)); // true
增强的对象语法
- 属性简写 ```javascript let name = ‘ccc’;
let person = { name: name }; console.log(person); // { name : ‘ccc’}
//以下代码与上面代码是等价的: let name = ‘Matt’; let person = { name }; console.log(person); // { name: ‘Matt’ }
//代码压缩程序会在不同作用域间保留属性名,以防止找不到引用。以下面的代码为例: function makePerson(name) { return { name }; } let person = makePerson(‘Matt’); console.log(person.name); // Matt //在这里,即使参数标识符只限定于函数作用域,编译器也会保留初始的 name 标识符。如果使用 //Google Closure 编译器压缩,那么函数参数会被缩短,而属性名不变: function makePerson(a) { return { name: a }; } var person = makePerson(“Matt”); console.log(person.name); // Matt
2. 可计算属性```javascriptconst nameKey = 'name';const ageKey = 'age';const jobKey = 'job';let person = {[nameKey]: 'Matt',[ageKey]: 27,[jobKey]: 'Software engineer'};const nameKey = 'name';const ageKey = 'age';const jobKey = 'job';let uniqueToken = 0;function getUniqueKey(key) {return `${key}_${uniqueToken++}`;}let person = {[getUniqueKey(nameKey)]: 'Matt',[getUniqueKey(ageKey)]: 27,[getUniqueKey(jobKey)]: 'Software engineer'};console.log(person); // { name_0: 'Matt', age_1: 27, job_2: 'Software engineer' }
- 简写方法名 ```javascript let person = { sayName: function(name) { console.log(‘my name is ${name}’) } } person.sayName(‘Matt’); // My name is Matt
//以下代码和上述的代码在行为上是等价的: let person = { sayName(name) { console.log(‘my name is ${name}’) } } person.sayName(‘Matt’); // My name is Matt
<a name="VtGHr"></a>### 创建对象<a name="JTkck"></a>### 继承<a name="U6oiS"></a>### 类<a name="pr33k"></a>## BOM<a name="lwxnM"></a>### window对象BOM的核心是window对象,表示浏览器的实例。window对象在浏览器中有两重身份,一个是ECMASCRIPT中的Global对象,另一个就是浏览器窗口的JavaScript接口。这意味着网页中定义的所有方法对象、变量和函数都是以window作为Global对象,都可以访问其上定义的parseInt()等全局方法。<a name="iCiSr"></a>#### Global 作用域因为window对象复用为ECMAScript的Global对象,所以通过var声明的全局变量和函数都会变成window对象的属性和方法```javascript// 通过var声明的全局变量和函数都会变成window对象的属性和方法var age = 29;var sayAge = () => alert(this.age);alert(window.age); // 29sayAge(); // 29window.sayAge(); // 29//使用let 或 const代替var,变量就不会添加给全局对象let age = 29;const sayAge = () => alert(this.age);alert(window.age); // undefinedsayAge(); // undefinedwindow.sayAge(); // TypeError: window.sayAge is not a function
视口位置
窗口大小
innerWidth、innerHeight、outerWidth 和 outerHeight。outerWidth 和 outerHeight 返回浏
览器窗口自身的大小
导航与打开新窗口
window.open()方法可以用于导航到指定URL,也可以用于打开新浏览器窗口。这个方法接受4个参数:要加载的URL、目标窗口、特性字符串和表示新窗口在浏览器历史记录中是否替代当前加载页面的布尔值。通常,调用这个方法时只传前3个参数,最后一个参数只有在不打开新窗口时才会使用
定时器
setTimeout
JavaScript再浏览器中时单线程执行的,但允许使用定时器指定某个时间之后或每隔一段时间就执行相应的代码。setTimeout()用于指定在一定时间后执行某些代码,而setInterval()用于指定每隔一段时间执行某些代码。
setTimeout()方法通常接受两个参数:要执行的代码和要执行回调函数前等待的时间(毫秒)。第一个参数可以是包含JavaScript代码的字符串或者一个函数,比如:
setTimeout(() => alert("Hello world!"), 1000);
第二个参数是要等待的毫秒数,而不是要执行代码的确切时间。JavaScript 是单线程的,所以每次 只能执行一段代码。为了调度不同代码的执行,JavaScript 维护了一个任务队列。其中的任务会按照添加到队列的先后顺序执行。setTimeout()的第二个参数只是告诉 JavaScript 引擎在指定的毫秒数过后把任务添加到这个队列。如果队列是空的,则会立即执行该代码。如果队列不是空的,则代码必须等待前面的任务执行完才能执行。
调用setTimeout()时,会返回一个ID,可用于取消该任务。
可以调用clearTimeout()方法并传入超时ID
// 设置超时任务let timeoutId = setTimeout(() => alert("Hello world!"), 1000);//取消超时任务clearTimeout(timeoutId)
只要是在指定时间到达之前调用 clearTimeout(),就可以取消超时任务。在任务执行后再调用 clearTimeout()没有效果。
setInterval
setInterval()与setTimeout()的使用方法类似,只不过指定的任务会每隔指定时间就执行一次,直到取消循环定时或者页面加载。setInterval()同样可以接收两个参数:要执行的代码(字符 串或函数),以及把下一次执行定时代码的任务添加到队列要等待的时间(毫秒)。下面是一个例子:
setInterval(() => alert("Hello world!"),1000)
location对象
location 是最有用的 BOM 对象之一,提供了当前窗口中加载文档的信息,以及通常的导航功能。 这个对象独特的地方在于,它既是 window 的属性,也是 document 的属性。也就是说, window.location 和 document.location 指向同一个对象。location 对象不仅保存着当前加载文档的信息,也保存着把 URL 解析为离散片段后能够通过属性访问的信息。这些解析后的属性在下表中
有详细说明(location 前缀是必需的)。
假设浏览器当前加载的 URL 是 http://foouser:barpassword@www.wrox.com:80/WileyCDA/?q=
javascript#contents,location 对象的内容如下表所示。
