js基础骚操作

null和undefined区别

  • 例子
// 在代码中
Number(null); // 0
5 + Number(null); // 5
Number(undefined); // NaN

对于null和undefined,大致可以像下面这样理解。

null表示空值,即该处的值现在为空。调用函数时,某个参数未设置任何值,这时就可以传入null,表示该参数为空。比如,某个函数接受引擎抛出的错误作为参数,如果运行过程中未出错,那么这个参数就会传入null,表示未发生错误。

整数和浮点数

  • JavaScript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此。所以,1与1.0是相同的,是同一个数。
1 === 1.0 // true
  • JavaScript 会自动把64位浮点数,转成32位整数,然后再进行运算
  • js运算有效范围在 即-2的53次方到2的53次方

数值的精度

(-1)^符号位  *  1.xx...xx  *  2^指数部分

上述

根据国际标准 IEEE 754,JavaScript 浮点数的64个二进制位,从最左边开始,是这样组成的。

  • 第1位:符号位,0表示正数,1表示负数

  • 第2位到第12位(共11位):指数部分

  • 第13位到第64位(共52位):小数部分(即有效数字)
    所以 js运算有效范围在 即-2的53次方到2的53次方

  • 任何数都有正负数 , 包括0

  • 绝大多数的情况下 , 除了除法 , 其他运算和0运算都是一样的 , 例子如下

1 / +0 // Infinity 正无穷

1 / -0 // -Infinity // 负无穷
1 / -0 === 1 / +0 // false

Infinity 特殊运算

5 * Infinity ;// Infinity
0 * Infinity; // NaN

Infinity - Infinity; // NaN
Infinity / Infinity; // NaN

  • Infinity与null计算时,null会转成0,等同于与0的计算。Nnumber(null)输出0
  • Infinity与undefined计算,返回的都是NaN

parseInt 和 parseFloat

  • parseInt 如果第二个参数不是数值,会被自动转为一个整数。这个整数只有在2到36之间,才能得到有意义的结果,超出这个范围,则返回NaN。如果第二个参数是0、undefined和null,则直接忽略。
  • parseFloat除了不接受第二个参数和特定的进制外 , 其他使用和parseInt一样

isNaN()

isNaN('Hello')
// 相当于
isNaN(Number('Hello'))

isFinite()

判断是否为一个正常数值

Base64 转码

  • btoa():任意值转为 Base64 编码
  • atob():Base64 编码转为原来的值

表达式还是语句?

当首行是大括号开头时 , js引擎会解析成代码块 , 想要解析成对象 , 就可以使用以下方式`


({ foo: 123 }) // 正确
({ console.log(123) }) // 报错

对象属性操作

var obj = {};
delete obj.p // true

上面代码中,对象obj并没有p属性,但是delete命令照样返回true。因此,不能根据delete命令的结果,认定某个属性是存在的。

属性是否存在:in 运算符

var obj = { p: 1 };
'p' in obj // true'toString' in obj // true , 会自动往原型上面查找 , 所以得使用 hasOwnProperty 判断是否自身

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

for...in

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

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

// toString 属性是存在的 , 但是默认是不可遍历属性
obj.toString // toString() { [native code] }

for (var p in obj) {
  console.log(p);
} // 没有任何输出
  • 应该结合使用hasOwnProperty方法,在循环内部判断一下,某个属性是否为对象自身的属性。

函数名的提升


f();

function f() {} // 不报错


f();
var f = function (){};
// TypeError: undefined is not a function


// 例子1和例子2解释了如果同时采用function命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。

// 例子2和例子3解释了 , 使用赋值语句声明的函数 , 如果执行函数在前面那么会执行同名的 function命令 声明的函数
{
// 例子1
    var f = function () {
      console.log('1');
    }

    function f() {
      console.log('2');
    }

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

{
// 例子3
    f() // 输出2
    var f = function () {
      console.log('1');
    }

    function f() {
      console.log('2');
    }
}

函数name属性

var f3 = function myName() {};
f3.name // 'myName'


var f2 = function () {};
f2.name // "f2"

运算符

这里特地说一下 + 运算符
对于引用类型相加
首先两个引用类型相加 , 都会经过一系列的转换才会去相加 , 最后都会转换成基本类型

var obj = {}
obj + 2 // "[object Object]2"

他是怎么相加的呢
引用类型首先会调用对象的valueOf方法。再自动调用对象的toString方法 , 这就是他的原理
但是也有特殊情况

{} + [] // 0 , 这里 , 前面的 {} 会被js引擎当成代码块 , 这里实际只是 +[] 
[] + {} // "[object Object]"

还有一种特殊情况就是 , 当引用类型是function时 , 又会去会调用对象的valueOf方法。再自动调用对象的toString方法
例子

{}+ function a(){} // "[object Object]function a(){}"

还有一种特殊情况 , 引用类型为Date时 , Date直接调用toString方法

var obj = new Date();
obj.valueOf = function () { return 1 };
obj.toString = function () { return 'hello' }; // 我们可以对toString直接改写

obj + 2 // "hello2"

自增和自减运算符

执行该运算子之前 , 都会先对前面需要运算的函数或者值进行Number处理 , 而且会改变原始值
例如当 当前是引用类型错误的时候

var A = {}
A++ //NaNconsole.log(A) // NaNvar a = function(){}
a++  //NaNconsole.log(a) // NaN

指数运算符

这个指数运算符相当于使用Math.pow(x,y)
例子

2 ** 3// 跟下面相等Math.pow(2,3)

特殊情况 , 指数运算符是右结合,而不是左结合。即多个指数运算符连用时,先进行最右边的计算

2 ** 3 ** 2// 512// 相当于 2 ** (3 ** 2)

比较运算符

比较大小的算法是

  1. 当两个比较的运算子为字符串的时候 按照字典顺序比较(实际上是比较 Unicode 码点)
  2. 其他情况 , 将两个运算子都转成数值,再比较数值的大小。

其他运算 ,
NaN , 所有类型的数值和NaN 比大小都是false
对象和引用类型的类型的值 , 会转换成原始类型的值再做比较
先调用 valueOf() 再调用 toString() ,
还有其他比较 , 例如

false == 'false'    // falsefalse == '0'        // true

false == undefined  // falsefalse == null       // falsenull == undefined   // true

不相等运算符相等运算符有一个对应的“不相等运算符”(!=),它的算法就是先求相等运算符的结果,然后返回相反值。

1 != '1' // false

// 等同于
!(1 == '1')

布尔运算符

!!x
// 等同于Boolean(x)

短路运算
如下

if (i) {
  doSomething();
}

// 等价于

i && doSomething();

二进制位运算符

二进制位运算符用于直接对二进制位进行计算,一共有7个。

  1. 二进制或运算符(or):符号为|,表示若两个二进制位都为0,则结果为0,否则为1。
  2. 二进制与运算符(and):符号为&,表示若两个二进制位都为1,则结果为1,否则为0。
  3. 二进制否运算符(not):符号为~,表示对一个二进制位取反。
  4. 异或运算符(xor):符号为^,表示若两个二进制位不相同,则结果为1,否则为0。
  5. 左移运算符(left shift):符号为<<,详见下文解释。
  6. 右移运算符(right shift):符号为>>,详见下文解释。
  7. 头部补零的右移运算符(zero filled right shift):符号为>>>,详见下文解释。

这些位运算符直接处理每一个比特位(bit),所以是非常底层的运算,好处是速度极快,缺点是很不直观,许多场合不能使用它们,否则会使代码难以理解和查错

!!!注意点1 有一点需要特别注意,位运算符只对整数起作用
!!!注意点2 做位运算的时候,是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数
!!!注意点3 位运算只对整数做运算 , 且会自动去除小数点 , 且内部js引擎会调用Number()方法

// 利用这个特性,可以写出一个函数,将任意数值转为32位整数function toInt32(x) {
  return x | 0;
}

异或运算符

异或运算(^)在两个二进制位不同时返回1,相同时返回0
例子

// 表达式中,0(二进制00)与3(二进制11)进行异或运算,它们每一个二进制位都不同,所以得到11(即3)0 ^ 3 // 3

还有另外一个超实用的用法 , 就是不使用临时变量的情况下 , 互换两个变量的整数

var a = 10;
var b = 99;

a ^= b, b ^= a, a ^= b;

a // 99
b // 10

类型转换

parseInt逐个解析字符,而Number函数整体转换字符串的类型。
一般来说Number非数字字符串 , 就会返回NaN

Number原理

第一步,调用对象自身的valueOf方法。如果返回原始类型的值,则直接对该值使用Number函数,不再进行后续步骤。
第二步,如果valueOf方法返回的还是对象,则改为调用对象自身的toString方法。如果toString方法返回原始类型的值,则对该值使用Number函数,不再进行后续步骤。
第三步,如果toString方法返回的是对象,就报错。

String方法 , 原理

与Number方法基本相同,只是互换了valueOf方法和toString方法的执行顺序。

  1. 先调用对象自身的toString方法。如果返回原始类型的值,则对该值使用String函数,不再进行以下步骤。
  2. 如果toString方法返回的是对象,再调用原对象的valueOf方法。如果valueOf方法返回原始类型的值,则对该值使用String函数,不再进行以下步骤。
  3. 如果valueOf方法返回的是对象,就报错。

Boolean() 原理

Boolean()函数可以将任意类型的值转为布尔值。

它的转换规则相对简单:除了以下五个值的转换结果为false,其他的值全部为true。

  1. undefined
  2. null
  3. 0(包含-0和+0)
  4. NaN
  5. ''(空字符串)

关于try .... catch .... finally

例子

try {
    throw '出错了!';
  } catch(e) {
    console.log('捕捉到内部错误');
    throw e + '这里是catch'; // 这句原本会等到finally结束再执行 , 但由于finally有return语句 , 就不会执行
  } finally {
    return false; // 直接返回
  }
  /* 输出如下
  捕捉到内部错误 ------ > 这里是catch执行的逻辑
 false ----> 这里是finally return 返回的false
  */
  
  // 如果我们不在finally使用return语句 , 那么就是执行catch里面抛出错误的逻辑 , 例子如下
  try {
    throw '出错了!';
  } catch(e) {
    console.log('捕捉到内部错误');
    throw e + '这里是catch'; // 这句原本会等到finally结束再执行 , 但由于finally有return语句 , 就不会执行
  } finally {
    console.log('finally'); // 执行这里会 , 返回catch里面抛出错误
  }

编写风格

对switch函数改写

switch...case结构要求,在每一个case的最后一行必须是break语句,否则会接着运行下一个case。这样不仅容易忘记,还会造成代码的冗长。

而且,switch...case不使用大括号,不利于代码形式的统一。此外,这种结构类似于goto语句,容易造成程序流程的混乱,使得代码结构混乱不堪,不符合面向对象编程的原则。
可以使用以下改写风格

// 原switch case

function doAction(action) {
  switch (action) {
    case 'hack':
      return 'hack';
    case 'slash':
      return 'slash';
    case 'run':
      return 'run';
    default:
      throw new Error('Invalid action.');
  }
}

// 改写如下

function doAction(action) {
  var actions = {
    'hack': function () {
      return 'hack';
    },
    'slash': function () {
      return 'slash';
    },
    'run': function () {
      return 'run';
    }
  };

  if (typeof actions[action] !== 'function') {
    throw new Error('Invalid action.');
  }

  return actions[action]();
}

Object 对象

Object.prototype.toString.call(obj)判断类型
不同数据类型的Object.prototype.toString.call(obj)方法返回值如下。

  1. 数值:返回[object Number]。
  2. 字符串:返回[object String]。
  3. 布尔值:返回[object Boolean]。
  4. undefined:返回[object Undefined]。
  5. null:返回[object Null]。
  6. 数组:返回[object Array]。
  7. arguments 对象:返回[object Arguments]。
  8. 函数:返回[object Function]。
  9. Error 对象:返回[object Error]。
  10. Date 对象:返回[object Date]。
  11. RegExp 对象:返回[object RegExp]。
  12. 其他对象:返回[object Object]。

this

  • 将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

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

call

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

var obj = {};

var f = function () {
  return this;
};

f() === window // true  ----> this指向window
f.call(obj) === obj // true ---> this指向obj

call环境如果是null , undefined , 或者不传参数 , 那么就是指向window

var n = 123;
var obj = { n: 456 };

function a() {
  console.log(this.n);
}

a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(obj) // 456

如果call方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后传入call方法。

例子

var f = function () {
  return this;
};

f.call(5)
// Number {[[PrimitiveValue]]: 5}

call第一个参数是作用域 , 其余参数是对象接收的参数 , ,不返回新的函数
apply第一个参数是作用域, 第二个参数是数组作为对象的参数传进去 , 不返回新的函数
bind , 使用方式和call一样 , 但是会返回新的函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值