JS知识点整理(一)
前言
本文把平时的一些读书笔记和理解进行了整理归纳,包含一些易混淆、遗漏的知识点,也会配上一些例子,可能不是很完整,还会有点杂,但也许会有你需要的(目前先整理了一部分,笔记有点多,后续会持续更新)。
一、变量
1.1 变量声明
1.变量是常见的标识符,以字母、$、_开头,但是不能包含+ - *等标识符,中文也是合法的标识符,保留字不能作为标识符。
2.函数声明和赋值会分为两个阶段,一个是编译阶段的任务,就是编译器声明变量,另一个是执行阶段的任务,就是js引擎去查询赋值,所以声明都会在代码被执行前首先进行处理。
3.如果在函数中使用var定义一个变量,函数调用时相当于创建了这个变量并赋值,在函数退出后就会被销毁,所以在函数外面读不到这个变量。但是如果在函数内定义变量时不使用var,相当于是全局变量,外面一般是读得到的,但是如果不运行这个函数时,相当于没有创建这个变量,外面也是读不到的:
function test(){
message = 'hello'
}
test(); // 如果不先运行这个,则读不到message变量
alert(message);
4.var声明的变量不能用delete删除 ,delete可以删除一个对象的属性,但不能删除继承的属性。
5.变量没有类型,变量的值才有类型。
1.2 变量提升
1.提升是指声明会被视为存在于其所出现的作用域的整个范围内,就是说在哪个作用域声明的,它就在这个作用域内(let, const并不会提升)。
2.函数声明提升:
foo(); // typeError 相当于对undefined进行函数执行,所以是类型错误
bar(); // referenceError 不存在这个函数,所以是引用错误
var foo = function bar() {} // 函数表达式不会提升
3.函数提升的优先级大于变量提升的优先级,即函数提升在变量提升之上。
// 例一:
var fo;
fo = function(){
console.log(456);
}
function fo(){
console.log(123);
}
fo(); // 456 函数声明被提升到了最上面,然后被后面的函数覆盖
// 例二:
fo = function(){
console.log(456);
}
fo(); // 456 同理,函数也是被覆盖
var fo;
function fo(){
console.log(123);
}
// 例三:
var fo = function(){
console.log(456);
}
function fo(){
console.log(123);
}
fo(); // 456 同理,函数也是被覆盖
// 同时函数声明还能被后面的声明覆盖,说明越后面的函数声明排在越后面,然后把前面的函数覆盖了:
foo(); // 3
function foo() {
console.log(1);
}
var foo = function() {
console.log(2);
}
function foo() {
console.log(3);
}
4.继续例子
console.log(foo); // 打印出函数源码
foo(); //可以执行 undefined 和 12
var foo=10;
foo(); //foo已经被赋值为一个变量,无法执行foo为函数,所以报错
console.log(foo); // 上面报错,这里执行不到
function foo(){
var a;
console.log(a);
a=12;
console.log(a);
}
console.log(foo);
//以上实际执行顺序:
function foo(){
var a;
console.log(a);
a=12;
console.log(a);
}
var foo;
console.log(foo);
foo();
foo=10;
foo(); //由于这里报错,foo已经被赋值,找不到这个函数,下面的都不会被执行
console.log(foo);
console.log(foo);
1.3作用域
1.31 全局/函数/块作用域
1.作用域是一套规则(或者理解为约束范围),用于确定在何处以及如何查找变量,其实也就是查找变量的地方。
2.每个环境中都有一个变量对象,所以该环境下定义的变量和函数都保存在这个对象中。
3.当代码在一个环境中执行时,会产生作用域链,如函数的作用域链的第一个对象是arguments对象(arguments在全局中是不存在的),第二个对象是上一层环境,即该环境的外部环境,以此类推,全局对象是作用域链中的最后一个对象。
4.if和for中用var声明的变量都是全局变量。
5.循环变量比较:
// 例一:
for(var i = 0; i < 3; i++) {
console.log(i); // 0 1 2
}
console.log(i); // 3
// 例二:
for(var i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i); // 后面再输出三次4
}, 0)
}
console.log(i); // 这个先输出,为4
// 例三:
for(let i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i); // 1 2 3 let在每次迭代中都重新声明
}, 0)
}
6.函数参数也是属于函数局部作用域的,它们是隐式创建的。
7.块作用域:
- ① with()可将代码的作用域设置到一个特定的对象中。
- ② try/catch中catch也会创建块作用域,err只能在该块作用域中访问。
- ③ let/const。
以上方式可将变量绑定在一个块中,形成这个变量的块作用域。
块作用域还利于垃圾回收,方便引擎明确哪块作用域的变量可以进行回收。
8.暂时性死区:
{
typeof a; // undefined 未声明的反而不会报错
typeof b; // referenceErroe 暂时性死区
let b;
}
1.32 词法作用域
1.词法作用域意味着作用域是由书写代码时函数声明的位置决定的。
2.eval()和with()能欺骗词法作用域,而且还会影响性能(因为js引擎不能事先确定变量和函数的定义位置,即不能进行词法静态分析)。
3.函数在哪里调用没有关系,变量的位置在编译的词法分析阶段就确定了:
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo(); // 这里其实就是闭包,包含了该函数的词法作用域
}
bar(); // 1
// 函数调用位置更换:
bar(); // undefined 全局value虽然声明了,但是在bar函数调用的后面才进行赋值,所以是undefined
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
1.33 闭包
1.可参考我写的这篇博客:[闭包]该如何理解?
1.4 条件语句
1.switch语句可判断值、字符串、表达式等,且是全等比较,不会产生类型转换(其实是===不允许进行类型转换)。
2.其实没有else if这个规范,其实真正是:
else{ // 省略了{}这个块而已
if () {
} else {
}
}
3.switch:
// 例一:
switch(a){
case 1: // case不能放逻辑表达式,只能是常量表达式,即只能进行单一的===对比
// code
break;
case 2:
// code
break;
default:
// code
}
// 例二:
var b = '42';
switch(true){
case b == 2: // 要进行类型转换比较则采取这种方式
// code
break;
case b == 42:
// code
break;
case b > 30: // 大小比较
// code
break;
default:
// code
}
// 例三:
var c = '42';
switch(c){
case 1:
case 2:
default:
console.log('default')
case 3:
console.log('3');
break;
case 4:
console.log('4')
}
// 会得到'default' '3', 首先会遍历所有的case,然后没有符合的case,然后执行default,之后没遇到break就继续往下执行case 3,最后到break停止。
1.5循环遍历
1.for循环中先把范围值保存为变量,优化循环。
2.do while
do {
// code
} while ('条件'); // 记得加上后面的分号
3.遍历对比:
自身:
- ① Object.keys返回对象自身的可枚举属性(数组)。
- ② Object.getOwnPropertyNames返回对象自身可枚举和不可枚举属性(数组),如数组的length属性。
- ③ hasOwnPropery()遍历自身可枚举和不可枚举属性。
自身和原型:
- ① in操作符能访问自身和原型上的可枚举和不可枚举属性。
- ② for in 能访问自身和原型上的可枚举属性,不含不可枚举属性。
4.循环更改格式:[ [ {}, {} ,{} ], [ {}, {} ,{} ], [ {}, {} ,{} ] ] 如果要转为[ [ ], [ ], [ ] ] 或者其他格式,可先用concat()方法把数组中的数组先循序连接起来,然后再用其他方法处理。
5.可以使用while来判断循环,直到不满足条件后才停止判断运行,比for循环直观方便,如:
while( i<5 ) { // i为大小不确定值
// code
i++;
}
6.中断循环:forEach不能中断,for可以。
7.break和continue:break能跳出循环,但也只能跳出一层循环,如果需要跳出多层循环,则需要使用标签;continue能跳过本轮循环,继续下轮循环:
top:
for (var i = 0; i < 3; i++){
for (var j = 0; j < 3; j++){
if (i === 1 && j === 1) break top;
console.log('i=' + i + ', j=' + j);
}
}
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0
// 标签相当于定位符,用于跳转到程序的任意位置,语式如下:
label:
二、数据类型
2.1数据类型
1.基本类型:number 、string、boolean、null、undefined、symbol。
引用类型:object (包含子类型function、array)。
typeof function a(){}; // 'function'
2.typeof 检测能得到的类型:
- ①"undefined" —— 如果这个值没有定义
- ②"boolean" —— 如果这个值是布尔值
- ③"object" —— 如果这个值是对象或者null
- ④"string" —— 如果这个字是字符串
- ⑤"number" —— 如果这个值是数字
- ⑥"function" —— 如果这个值是函数
- ⑦"symbol" —— ES6引入的一种新的原始数据类型Symbol,表示独一无二的值
2.2 Number
1.JavaScript 语言的底层根本没有整数,所有数字都是小数(64位浮点数)。
2.在JavaScript内部,整数和浮点数是同样的储存方法,所以3和3.0被视为同一个值:
3.0 === 3; // true
3.数字的二进制浮点数没那么精确:
0.1 + 0.2 === 0.3; // false
4.数字范围:2的1024次方到2的-1023次方。
5.最大的安全整数:2^53。
6.parseInt方法用于将字符串转为整数。如果parseInt的参数不是字符串,则会先转为字符串再转换。字符串转为整数的时候,是一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分。而isNaN是先将整体参数转化为number再进行判断:
parseInt('abc'); // NaN
parseInt('.3'); // NaN
parseInt(''); // NaN
parseInt('+'); // NaN
parseInt('+1'); // 1
parseInt('1000', 2); // 8 可接受第二个进制参数
7.toFixed注意事项:
typeof 12.236.toFixed(2); // 'string' 指定保留位数后数字变成了字符串
42.toFixed(3); // 会报错,因为会把小数点算为42的一部分,需要用使用以下方式:
(42).toFixed(3);
42.0.toFixed(3)
42..toFixed(3)
8.判断是否为整数:
// 方式一:
typeof a === 'number' && num%1 === 0
// 方式二:
Number.isInteger(a);
// 方式三:
Number.isSafeInteger(a);
9.NaN:
NaN === NaN // false
NaN !== NaN // true
Boolean(NaN) // false
typeof NaN // 'number'
// 判断是否为NaN:
// 原生方式:
Number.isNaN(n);
// 自定义方式:
function isNaN(n) {
return n !== n;
}
2.3 String
1.字符串可使用方括号[ ]来获取该位置上的字符,也可查询长度,但无法像数组一样增加或删除字符。
2.数字字符串转数字的一种方式:字符串与1想乘:"2" * 1
3.数字转字符串的一种方式:数字加空字符串: 1 + ""
4.拆分:
'a b c'.split(' ');
// ['a', 'b', '', '', 'c'] b和c之间有三个空格,所以三个空格会得到夹着的两个空格。
5.字符串没有map()、join()等方法,但可以通过Array.prototype.call()来借用,reverse()不可借用,可先转为数组再使用。
6.截取:
-
①substring() :
参数如果是负数,会变为0,如果第一个参数大于第二个参数,则会调换位置。 -
②substr():
第一个参数是开始位置,第二个是长度,如果第一个参数是负数,则表示倒数计算的字符位置,如果第二个参数是负数,则返回空字符串(相当于返回长度为0)。 -
③match():
用于确定原字符串是否匹配某个子字符串,返回一个数组,成员为匹配的第一个字符串。如果没有找到匹配,则返回null。 -
④search():
search方法的用法基本等同于match,但是返回值为匹配的第一个位置。如果没有找到匹配,则返回-1。
2.4 Boolean
1.六个false值:“”,null, undefined, 0, NaN,false。
2."''"不是空字符串,是true。
3.document.all是ie中的假值对象,是false。
2.5 Null和Undefined
1.null和undefined的名称既是类型也是值,null指空值,曾赋过值,但目前为没有值,undefined指没有值,未赋值。
2.null是js中的一个bug,因为不同的对象在底层都表示为二进制,在js中二进制前三位都为0时就判断为object类型,而null的二进制表示是全0,所以执行typeof时会得到‘object’(或者把null当成一个空对象指针,所以typeof检测会得到object)。
3.nullundefined 是true,且不进行类型转换,因为它们是类似的值,且只有null和undefined是,和其它对比都是false,所以:
null == 0 // false
undefined == 0 // false
null == '' // false
undefined == '' // false
null == fasle // false
undefined == false // fasle
// 只有自身转化才会是布尔值:
!null == true // true
!undefined == true // true
// 但运算时,null会转型为0,undefined转为NaN:
null + 3 // 3
undefined + 3 // NaN
// 判断类型:
typeof null // 'object'
typeof undefined // 'undefined'
2.6 广义Object
2.6.1 狭义对象
1.所有的对象都是继承自Object的,都会带有这些属性,toString(), valueOf(), constructor。
2.创建对象 :
// ①字面量方式:
var o = {};
// ②Object.create方式:
Object.create('这个对象的原型,对象属性的描述')
Object.create({x:1,y:2}) // 继承了这个对象的属性
Object.create(null) // 则不会继承任何属性和方法;
Object.create(Object.prototype) // 和new Object()一样创建了一个空对象
// ③构造函数方式:
function createPerson (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
alert(this.name);
}
}
// ④工厂模式方式:
function createPerson (name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
alert(this.name);
}
return o;
}
构造函数与工厂模式区别:
- ①没有显示的创建对象。
- ②直接将属性和方法赋予给了this对象。
- ③没有return语句。
构造函数过程:
- ①创建一个空对象。
- ②将构造函数的作用域赋给空对象(this指向空对象,空对象原型指向构造函数原型)。
- ③执行代码(为这个空对象添加属性)。
- ④返回新对象(return this)。
构造函数之所以叫“构造函数”,就是说这个函数的目的,就是操作一个空对象(即this对象),将其“构造”为需要的样子。构造函数中如果有return一个对象,则new这个构造函数时得到的是这个对象,而不是新的this对象(可以认为构造函数内开头先定义了一个this变量,然后最后返回this)。
构造函数内的方法在每次生成实例的时候,实例对象内的函数都是新生成的,相当于new了一个函数,因为函数也是对象,所以每次生成实例对象内的函数的时候,就相当于定义了一个对象,所以虽然它们名字相同,但却是不同的函数。
// o指对象,返回的就是对象本身
o.valueOf() === o // true
// 数组也是如此,因为数组也是对象,也可以用构造器生成:
var a = new Array()
// 就像构造函数创建对象,所以a也会带有toString等属性,因为对象都会继承toString等方法。
4.访问对象属性:如果对象的属性是动态、不可预测或不符合变量规范的,用方括号的形式person["name"],而不能用点.方式,方括号内要放字符串,但当name是变量时,也可以用person[name]来访问属性。
5.检测属性:
- ①
in
// 检测属性是否存在于自身或原型上,含不可枚举属性 - ②
hasOwnProperty()
// 检测属性是否存在于自身,含不可枚举属性 - ③
propertyIsEnumerable()
// 检测属性是否存在于自身,不含不可枚举属性
6.对象转换:
// ①Object()可将任何值转为对象;Object()返回一个空对象;如果Object方法的参数是一个对象,它总是返回该对象:
var value = {};
var obj = Object(value) // 返回原对象
obj === value // true
// ②valueOf返回一个对象的“值”,默认情况下返回对象本身:
var obj = new Object();
obj.valueOf() === obj // true
// ③new Object(value)的值是value对象。
var o1 = new Object();
o1.toString() // "[object Object]" 注意大小写
数组、字符串、函数、Date 对象都分别部署了自定义的toString方法,覆盖了原生的Object.prototype.toString方法,原生的Object.prototype.toString()返回的是类:
// 数组:
[1, 2, 3].toString(); // "1,2,3"
// 字符串:
'123'.toString(); // "123"
// 函数:
(function () { return 123;}).toString(); // "function () { return 123; }"
// Date:
(new Date()).toString(); // "Tue May 10 2016 09:11:31 GMT+0800 (CST)"
8.对象是引用同一个地址:
var o1 = {};
var o2 = o1;
o1 = 1; // o1是改变了自己的指向,而不是改变{}
o2; // {} o2已指向{}的地址,只要这个地址的对象没有变,它就不会变
原始类型是值拷贝:
var x = 1;
var y = x;
x = 2;
y // 1
深拷贝:
// 方式一:
function deepCopy(obj) {
return JSON.parse(JSON.stringify(obj));
}
// 方式二:
function deepCopy (p, c) {
c = c || {};
for (let i in p) {
if (p.hasOwnProperty[i]) { // 排除继承属性
if (typeof p[i] === 'object') {
c[i] = Array.isArray(p[i]) ? [] : {};
deepCopy[p[i], c[i]];
} else {
c[i] = p[i];
}
}
}
return c;
}
9.对象属性描述符:
// configurable 能否修改属性
// enumerable 是否可枚举
// writable 能否修改属性的值
// value 属性的数据值
var myObject = { a: '2' };
var b = Object.getOwnPropertyDescriptor(myObject, 'a');
console.log(b); // {value: "2", writable: true, enumerable: true, configurable: true}
Object.defineProperty(myObject, 'a', {
value: '3',
writable: true, // 可修改
enumerable: true, // 可枚举
configurable: true // 可配置,如果为false,则不能修改以上配置,但可以//myObject.a= 5, writable可以变为false, 但不能变为true
})
10.get set
var other = {
get a(){
return this._a_;
},
set a(val){
this._a_ = val*2;
}
}
console.log(other); // {}
console.log(other.a); // undefined
other.a = 2;
console.log(other.a); // 4
11.Function.prototype是一个函数,RegExp.prototype是一个正则表达式,Array.prototype是一个数组,它们都是空的,(但总的来说它们都是对象)。
12.字符串要访问属性或方法时,js会自动将其转化包装对象,这种让js自行执行,而不要人为转为包装对象,否则会影响性能。
'abc'.length // 3
2.6.2 函数
1.函数中执行return语句后立刻退出,所以放在return语句后面的语句不会执行;未指定返回值的函数返回的是undefined。
2.函数的参数在内部是用数组来表示的,arguments是类数组,可用arguments[0]来获取参数,arguments的长度是由传入的参数个数决定的,而不是由命名参数的个数决定的;参数传递的都是值,传入引用类型值时注意是否有影响。
3.函数名称和参数长度:a.name , a.length是预期传入的参数个数,不是实际参数个数。
4.没有函数签名,所以没有重载,即同名函数会被后面发覆盖,但是能通过判断参数的类型和数量来作出不同的反应来模仿方法的重载。
5.函数实际上是对象,每个函数都是Function类型的实例,而且和其他应用类型一样具有属性和方法,如call、apply、length、contructor、prototype、name等属性,函数名实际上是指向函数对象的指针。
6.区分函数声明和表达式的方法是看function出现在声明的哪个位置,如果function是第一个词,就是函数声明,否则为函数表达式(自执行函数是以括号开头,所以为表达式)。
7.函数内有两个特殊的对象:
-
①arguments:
arguments有一个callee属性,是一个指针,指向拥有这个arguments对象的函数。 -
② this:
this引用的是函数据以执行的环境对象。
8.一些区别:
-
①函数声明:
function a() {}
可直接a()调用,花括号后面不用分号结尾。 -
②函数表达式:
let t = function() {}
可用t()调用,且一般不带函数名,如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效,花括号后面带分号:
var print = function x(){
console.log(typeof x);
}
x; // ReferenceError: x is not defined
print(); // function
- ③构造函数:
var add = new Function(
'x',
'y',
'return x + y'
)
// 等同于
function add(x, y) {
return x + y;
}
- ④函数执行时所在的作用域,是定义时的作用域,即词法作用域,而不是调用时所在的作用域:
var a = 1;
var x = function () {
console.log(a);
}
function f() {
var a = 2;
x();
}
f(); // 1
9.原生函数:
- Number(), String(), Boolean(),
- Object(), Array(), Function(),
- RegExp(), Date(), Error(), Symbol()
10.类型判断:
var a = new String('abc');
typeof a; // 'object' a为包装对象
a instanceof String // true
Object.prototype.toString(a); // '[object, Object]' 如果不使用call,不论传入什么,都为Object类,这个没什么用,如果要用于判断类型,都得加call
Object.prototype.toString.call(a); // '[object, String]' 让a调用Object上的toString方法,属于String类
11.返回
return ( // return如果有换行,需要加括号
a*2;
)
12.如果构造函数内部有return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象;否则,就会不管return语句,返回this对象:
// 例一:
var Vehicle = function (){
this.price = 1000;
return { price: 2000 };
}
(new Vehicle()).price; // 2000 如果不使用new,则this将变为全局的
// 例二:如果对普通函数(内部没有this关键字的函数)使用new命令,则会返回一个空对象:
function getMessage() {
return 'this is a message';
}
var msg = new getMessage();
msg // {}
typeof msg // "object"
2.6.3 数组
1.可通过设置数组的长度对数组进行增加或删减元素,也可以为数组添加字符串键,但不会算入数组长度(数字字符串除外),如:a['13'] = 'test'
2.检测是否为数组的两种方法:
- ①value instanceof Array
- ②Array.isArray(value)
3.对数组调用toString()方法时返回每个值的字符串以逗号连接起来的字符串,alert()接受的是字符串参数,所以:
var people=[per1,per2];
alert(people.valueOf());
// 返回的还是以逗号连接的字符串,因为会默认调用后台的toString()方法将people转为字符串
4.value.join("|"),不传参时默认以逗号连接。
5.返回值:
- push()、unshift()返回的是修改后的数组的长度
- pop()、shift()返回的是删除的项
6.sort()是高阶函数,可传入方法:
var arr = [2, 10, 20, 1];
arr.sort(function (x, y) { // 升序
if (x < y) {
return -1;
}
if (x > y) {
return 1;
}
return 0;
});
console.log(arr); // [1, 2, 10, 20]
//或者
function(x, y){
return x - y; // 升序
}
7.栈与队列:
- 栈方法:LIFO(last-in-first-out)后进先出push()和pop()。
- 队列方法:FIFO(first-in-first-out)先进先出push()和shift()。
8.数组原始值是否改变:
不改变原数组:(连接\截取\循环)
- concat:连接多个数组,返回新的数组
- join:将数组中所有元素以参数作为分隔符放入一个字符
- slice:slice(start,end),返回选定元素
- map,filter,forEach,some,every等不改变原数组
改变原数组:(增加\删除\换序)
- shift:将第一个元素删除并且返回删除元素,空即为undefined。
- unshift:向数组开头添加元素,并返回新的长度。
- pop:删除最后一个并返回删除的元素。
- push:向数组末尾添加元素,并返回新的长度。
- reverse:颠倒数组顺序。
- sort:对数组排序。(数组排序后和与排序前的数组绝对相等===,因为还是同一个引用)
- splice: splice(start,length,item)删,增,替换数组元素,返回被删除数组,无删除则不返回,如果只提供第一个参数,等同于将原数组在指定位置拆分成两个数组:
var a = [1, 2, 3, 4];
a.splice(2); // [3, 4]
a; // [1, 2]
9.delete删除数组元素,不会改变数组长度,该位置会变空为undefined。
10.类数组转数组:因为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);
11.reduce():
- 一参:累积变量,默认为数组的第一个成员
- 二参:当前变量,默认为数组的第二个成员
- 三参:当前位置(从0开始)
- 四参:原数组
应用:找出最长字符串:
function findLongest(entries) {
return entries.reduce(function (longest, entry) {
return entry.length > longest.length ? entry : longest;
}, '');
}
findLongest(['aaa', 'bb', 'c']); // "aaa"
12.数组去重:
let s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}
let arr = [..s];
let arrs = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];
arrs.filter(function (element, index, self) {
return self.indexOf(element) === index;
})
13.空单元数组:
// 例一:
var arr = ["a", "b", "c"];
console.log(arr['2']); // 'c'
arr['5'] = 'e';
console.log(arr.length); // 6
console.log(arr); // ["a", "b", "c", empty × 2, "e"]
// 例二:
var a = new Array(3);
console.log(a); // [empty × 3] 空单元数组
var b = [undefined,undefined,undefined]; // 这个不是空单元数组,是值为undefined的数组
// 例三:
var c = [];
c.length = 3;
console.log(c); // [empty × 3] 空单元数组
14.数组和字符串都有的方法:splice()、indexOf()、lastIndexOf()、contact()
2.7 类型转换
1.toString()转换为字符串,如果是数字,可传递基数来确认返回的是多少进制的数字字符串:
let num = 12;
num.toString(8) // 返回8进制字符串
2.alert
会调用对象的toString()
方法,alert(a)
和alert(a.toString())
是一样的。
3.对象转化:
-
①对象转数字默认先调用toValue()方法,如果返回的不是原始值,则再调用toString()方法。
-
②对象转字符串默认先调用toString()方法,如果返回的不是原始值,则再调用toValue()方法。
-
③日期对象转数字时会默认直接调用toString()方法。
4.null和undefined没有toString()方法,但是可以使用String()来将它们转为字符串。
5.Number转化:
Number({}); // NaN
Number({a: 1}); // NaN
Number([1, 2, 3]); // NaN
Number([]); // 0
Number([5]); // 5
String([]); // ""
6.使用:
var a;
typeof a; // undefined
typeof b; // undefined 尽管没声明b,但不报错,但如果使用b,则会报错
var c = {};
c.a // undefined 访问不存在的对象属性不会报错,所以也不能根据c.a===undefined来判断是否有a属性。
7.数组的toString方法(即Array.prototype.toString())被重新定义过,将所有单元字符串后用逗号连接起来,而原生的Object.prototype.toString()返回的是“类”。
var a = [1, 2, 3];
Object.prototype.toString(a); // "[object Object]" 返回“类”
console.log(a.toString()); // '1,2,3' 会先找到Array上的toString方法
console.log(Array.prototype.toString.call(a)); // '1,2,3' 让a调用Array上的toString方法
console.log(Object.prototype.toString.call(a)); // [object Array] 让a调用Object上的toString方法
8.JSON.stringify()可以将对象转化为字符串,但它本质上是如果对象中有toJSON方法就先调用toJSON方法将所要转化的对象换为安全的JSON值,把循环引用类型、函数、null、undefined等不符合json的值过滤掉,然后再交由toString方法来转化。
JSON.stringify()还可接受数组参数或函数参数,作用和toJSON类似,用于过滤对象:
var a = {
b: 42,
c: '36',
d: [1,2,3]
};
JSON.sringify(a, ["b", "c"]); // "{"b":42, "c":"36"}"保留传入数组中包含的对象属性
JSON.stringify(a, function(k, v) {
if (k !== "c"){
return v;
}
}) // "{"b":42, "d":[1,2,3]}"
9.parseInt:
parseInt(new String(42)) // 42 是数字
因为parseInt是针对字符串的,所要parseInt会先调用所传入参数的toString方法将参数转化为字符串,然后再解析数字。
10.在存在布尔值中使用或=进行比较,或数字与字符串比较时,都是先将不是数字的那方先转换为数字(转为数字的优先级:布尔值>对象>字符串>数字),然后再进行比较:
var a = '123';
var b = true;
a == b; // false
//因为123不等于1,会先将布尔值转为1,然后和'123'比较,然后将'123'转为123再进行最后比较,所以“存在”和“==true”是不一样的,比如:
var a = '42';
// if(a) 和 if(a==true) 不一样。
11.进行算数运算时,优先调用valueOf()方法来转化数字,如果未转化成原始类型,则再调用toString()方法,但是Data对象是优先调用toString()方法。
最后
因为比较多,所以目前只整理到这里,后续有些比较重要难懂的模块会分开更新,希望对你有所帮助,如有不合理的地方欢迎指正,最后顺口广告来一波:大家好!我是BetterMan, to be better, to be man, better关注BetterMan!