Coder Social home page Coder Social logo

blog's People

Contributors

yuyue94 avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar

blog's Issues

深入理解Event Loop机制

Event Loop

参考资料

从setTimeout 0开始

对比两段代码

function get(id) {
    return document.getElementById(id);
}

//第一个例子:不使用setTimeout
get('makeinput').onmousedown = function () {
    var input = document.createElement('input');
    input.setAttribute('type', 'text');
    input.setAttribute('value', 'test1');
    get('inpwrapper').appendChild(input);
    input.focus();
    input.select();
}
//第二个例子:使用setTimeout
get('makeinput2').onmousedown = function () {
    var input = document.createElement('input');
    input.setAttribute('type', 'text');
    input.setAttribute('value', 'test1');
    get('inpwrapper2').appendChild(input);
    //setTimeout
    setTimeout(function () {
        input.focus();
        input.select();
    }, 0);
}
  • 不使用setTimeout 0则input没有focus,也没有select
  • 使用了setTimeout 0才是正常的行为
  • setTimeout 0保证了在render之后才执行了input的两个操作,这是怎么做到的

初识event loop

task queue

  • js是单线程的,浏览器的js主线程也可以称之为浏览器内核,event loop通过挑选task queue的机制使得程序有序进行
  • task有哪些类型
    • Events
    • Parsing
    • Callbacks
    • Using a resource
    • Reacting to DOM manipulation
  • 同一个Document下对应一个特别的event loop
  • event loop中有多个task queue,一个task queue对应一种task source,来自相同的task source则放入相同的task queue
  • 同一个task queue中保证执行顺序,但对于event loop,不同的task queue可以是交错执行

event loop 流程

An event loop must continually run through the following steps for as long as it exists:

  1. Run the oldest task on one of the event loop's task queues, if any, ignoring tasks whose associated Documents are not fully active. The user agent may pick any task queue.

  2. If the storage mutex is now owned by the event loop, release it so that it is once again free.

  3. If a task was run in the first step above, remove that task from its task queue.

  4. If this event loop is not a worker's event loop, run these substeps:

    1. Perform a microtask checkpoint.

    2. Provide a stable state.

    3. If necessary, update the rendering or user interface of any Document or browsing context to reflect the current state.

  5. Otherwise, if this event loop is running for a WorkerGlobalScope, but there are no events in the event loop's task queues and the WorkerGlobalScope object's closing flag is true, then destroy the event loop, aborting these steps.

  6. Return to the first step of the event loop.

以上大致意思是:执行task,释放储存锁,移除task,做microtask,渲染UI(其中的worker指的是web worker,web worker是实现的真正的多线程,一个线程就有一个event loop,所以在多线程的情况下如果没有事件,就要移除这个线程的event loop)

一个task的粒度是多大呢,microtask是什么,我们接下来讨论

macrotask vs. microtask

  • macrotask就是我们上面提到的task,microtask在es2015中也叫job
  • macrotask包括
    • 平台代码
    • setInterval里面的回调
    • setTimeout里面的回调
    • setImmediate里面的回调
    • I/O, UI rendering
  • microtask包括
    • process.nextTick里面的回调
    • Promise.then里面的回调
    • MutationObserver
  • 执行setTimeout只是将回调加入到新的macrotask队列里面,等待下一个loop执行
  • 执行Promise.resolve只是将.then之后的回调加入到microtask队列里面,等待当前task执行完毕后执行
(function test() {
    // 1⃣️ task A 执行中
    // 2⃣️ tasks 队列压入新的 task B
    setTimeout(() => {
      // microtasks 队列为空,于是检查 tasks 队列,取出 B并执行了
      console.log(4)
    }, 0)

    new Promise(resolve => {
        console.log(1)
        for( var i=0 ; i<10000 ; i++ ) {
            i == 9999 && resolve()
        }
        // 3⃣️ task A 继续执行
        console.log(2)
    })
    // 4⃣️ microtasks 队列压入 microtask a
    .then(() => {
        // 6⃣️ microtask a 执行中
        console.log(5)
        Promise.resolve(7)
        // 7⃣️ microtasks 队列压入 microtask b
        .then(v => console.log(v))
        // microtask a 执行完毕
    })
    // 8⃣️ microtasks 队列压入 microtask c
    // 这个 then 执行完后继续检查 microtasks 队列,并一次执行 b,c
    .then(() => {
        console.log(6)
    })
    // 5⃣️ task A 执行完毕,检查 microtasks 队列,发现非空,执行 microtasks 队列的第一个 microtask a
    console.log(3)
})()

/**
1
2
3
5
7
6
4
*/

task的优先级

观察者优先级
在每次轮训检查中,各观察者的优先级分别是:
idle观察者 > I/O观察者 > check观察者。

  • idle观察者:process.nextTick
  • I/O观察者:一般性的I/O回调,如网络,文件,数据库I/O等
  • check观察者:setImmediate,setTimeout

setTimeout 和 setImmediate优先级

  • setTimeout的优先级高于setImmediate,但是如果任务执行完setTimeout的回调在延时后还没有加到任务队列中,那么setImmediate会优先执行

深入理解Object的类型转换

深入理解Object的类型转换

从我们知道的开始

首先我们知道Object类型的转换和两个函数toString()和valueOf()有关,那为什么有关呢?

为什么要转换?

为了更容易讲清楚,我选择的是Standard ECMA-262 5.1 Edition,先从一个双操作数的加号操作符开始

The Addition operator ( + )

operator

  • 从标准中我们看到,在进行相加操作前js对值做了ToPrimitive()的内置操作

ToPrimitive

toprimitive

  • 从标准中我们看到,当对Object进行ToPrimitive()操作的时候,是调用[[DefaultValue]]的内置方法,然后会传入一个hint作为PreferredType

[[DefaultValue]](hint)

defaultvalue1

defaultvalue2

  • 所以如果hint是String,先判断toString能否调用,再判断toString()的结果,是基本类型才返回,再判断valueOf能否调用,再判断valueOf()的结果,是基本类型才返回,否则报错。
  • 所以如果hint是Number(或者没有hint,默认是Number),先判断valueOf(),再判断toString()
  • 对于普通Object,默认用hint为Number的方式来转换,对于Date类型的Object,用hint为String的方式来转换

The Subtraction Operator ( - )

operator-

tonumber

  • 双操作数的减号操作符我们发现做的是ToNumber()的操作
  • 而对于Object来说,无非是将ToPrimary()后的值再ToNumber()

ToString

tostring

  • 注意Object.prototype.toString()有自己单独定义的算法,和ToString()不是同一个方法

string

var a = {};
a.toString = () => 100;

a.toString(); // 100;
String(a); // '100'

ToBoolean

toboolean

  • 简单粗暴

哪些需要转换,做怎样的转换呢?

显示转换

  • String()对应 ToString()
  • Number()对应 ToNumber()
  • Boolean()对应 ToBoolean()

隐式转换,大多出现在操作符运算中

我们发现不同的操作符有不同的转换规则,所以笔者这里给大家分个类,这里我们列举常见的操作符,暂时不考虑位操作符,看完整的描述,可以从标准的这里开始看

简单的直接放图

一元操作符

operator1

二元操作符

operator2

operator3

麻烦的单独说

operator4

  • 下图是我截取的比较算法的转换步骤

operator4-1

  • 关系操作符都遵从这个算法,会先ToPrimitive(),hint为Number
    • 这里指定了hint为Number,所以Date类型的对象也会遵从hint Number的转化方式,别忘了不指定的话Date类型默认遵从hint String的转化方式
  • 然后当双方不都是String类型时,再将双方ToPrimitive()的结果ToNumber(),完整算法在这里

operator5

  • 下图是我截取的双等号算法的对于Object类型的关键步骤

operator5-1

  • 双等号比较的时候,如果双方类型相同,和三等号的算法一样
  • 如果有一个是Object,而另一个不是,将Object进行ToPrimitive()操作
    • 有人问Boolean类型怎么没了,Boolean类型在前面分支已经转化为number类型了,完整算法在这里
  • 三等号比较就不说了,也不存在类型转化
// 普通的Object
var a = {}; // 普通类型的ToPrimitive会遵从hint Number的转换规则
a.toString = () => 100;
a.valueOf = () => '10';

a + 2; // '102' -> 相当于 '10' + 2 为 '102'
a + '2'; // '102'
a > 3; // true -> 进行ToPrimitive() hint为Number操作之后 -> 比较 '10' > 3 -> 不都是String类型,对'10'进行ToNumber(), 10 > 3为true 
a > '3'; // false -> 实际比较的是 '10' > '3' 第一个字符的ascii码小,直接为false
a == 100; // false -> 相当于 '10' == 100为false
// Date类型的Object
var b = new Date(); // Date类型的ToPrimitive会遵从hint String的转换规则
b.toString = () => 100; // 这里的搞怪为了测试
b.valueOf = () => '10';

b + 2; // 102 -> 加号是ToPrimitive(), 100 + 2 为102
b + '2'; // '1002' -> 相当于 100 + '2'为'1002'
b > 3; // true -> 进行ToPrimitive() hint为Number操作之后 -> 比较 '10' > 3 -> 不都是String类型,对'10'进行ToNumber(), 10 > 3为true 
b > '3'; // false -> 实际比较的是 '10' > '3' 第一个字符的ascii码小,直接为false
b == 100; // true -> 进行ToPrimitive()操作
// 有些你们神奇的测试姿势
{} + 1; // 1
// 有人会认为答案应该是'[object Object]1',因为对Object进行ToPrimary(),先看valueOf(),发现是自身,不是基本类型,再看toString(),发现是'[object Object]',返回这个值,然后再相加
// 但是问题出在编译器会认为前面的{}是一个代码块,后面是一元操作符加号和1,所以结果为1

{} + '1'; // 1
// 没错,这里证明了这个加号是一元的,将'1'转化为了number

var a = {};
a + 1; // '[object Object]1'
// 老铁,这次就对了

[1,2] + 1; // '1,21' 
// [1,2],先对Object进行ToPrimary(),先看valueOf(),发现是自身,不是基本类型,再看toString,Array的toString()相当于join(','),所以得到'1,2'再和1相加得到'1,21'

我能自定义这些转换规则吗?

es6中可以给对象设置一个属性,key是一个内置Symbol,value是个函数,函数中可以制定ToPrimary()的转换规则,key是Symbol.toPrimitive,也可以参考阮老师的es6入门-Symbol,以下代码是阮老师的代码,这里直接用了

let obj = {
  [Symbol.toPrimitive](hint) {
    switch (hint) {
      case 'number':
        return 123;
      case 'string':
        return 'str';
      case 'default':
        return 'default';
      default:
        throw new Error();
     }
   }
};

// 上方的三种hint就是我们在标准文档中看到的三种hint,根据hint的不同可以自定义return的值
2 * obj // 246 -> 乘号对应ToNumber(),先ToPrimitive(input argument, hint Number),再将基本值ToNumber()
3 + obj // '3default' -> 双操作符的加号对应ToPrimitive(),没有hint
obj == 'default' // true -> 双等号对应ToPrimitive(),没有hint
String(obj) // 'str' -> String对应ToString(),先ToPrimitive(input argument, hint String),再将基本值ToString()

一道小习题

// 实现一个add方法
function add() {
    // ...
}

// 满足以下类型的调用和计算
add(1)(2)(3); // 6

var addTwo = add(2);
addTwo + 5; // 7
addTwo(3) + 5; // 10
add(4)(5) + add(2); // 11

以下是答案,请小心拉取

function add(n){
  var fn = function(x) {
    return add(n + x);
  };
  
  fn.valueOf = function() {
    return n;
  };
  
  return fn;
}

vue组件之日志滚动窗口

项目地址

背景

  • 后台有连续输出的大量日志,需要前端来展示
  • 后台的日志可能通过前端轮询请求ajax,或者websocket输出到前端

最简单的想法

easy

  • 前端通过ajax轮询向后台请求日志,日志直接放到cache里面
    • “cache数组”名字叫cache
  • 每隔100毫秒从cache里面取出一条日志放到与日志组件绑定的数组里
    • 与组件绑定的“展示数组”叫output
    • 频率也可以根据cache当前缓存的日志数来调,不一定固定死100毫秒
    • 每次output更新,重绘组件,日志打出
      • 日志打出后再将scrollbar滚至底端就有滚动输出的效果了
      • 由于vue有列表渲染的复用机制,日志少的情况下,数组频繁更新导致页面频繁更新也不会卡
// cache是cache
// output是与组件双向绑定的数组
// toCache是请求获取到数据后的回调函数
// addLine是每次向outpuListShow数组加一条日志,组件即更新
let toCache = function(data) {
    if (data && data.length > 0) {
        [].push.apply(cache, data);
    }
}

let addLine = function() {
    let item = cache.shift();
    if (item) {
        output.push(item.line);
    }
    if (cache.length > 0) {
        let frequency = Math.min(1 / cache.length * 1000, 100);
        setTimeout(addLine, frequency);
    }
}
<!-- vue template -->
<div id="log">
    <div v-for="(line, index) in output" :key="index">
        <span class="line-index">{{index}}</span>
        <div v-if="line" class="log-line">
            {{line}}
        </div>
        <br v-else/>
    </div>
</div>

然而事实没那么简单。。。。

要考虑的

  1. 日志的总量可能十分庞大,可达几万行到十几万行

    • 这时候因为新增dom元素会导致巨大的回流(reflow)性能损耗,所以在运行时用户操作滚动条的时候会非常卡,组件刷新也会越来越卡
  2. 为了日志滚动输出的效果,会有组件刷新后滚动条自动沉底的操作,在运行时实时打日志和用户操作滚动条看已有日志会有冲突

  3. 单次来的日志量也可能很大,但也要保证滚动效果

    • 滚动输出时间可能太长,比如运行完毕动画还在滚

一些方案

问题1

  • 方案1:减少日志组件的dom元素以减少回流性能损耗,比如只用一个div或者pre元素,只更新text来刷日志

    • 治标不治本
    • 说好的滚动动画呢!!
    • jenkins就是这样搞的,但老的日志会定时干掉
  • 方案2:运用滑动窗口,每次只显示固定数量的日志,一个窗口(tick)固定容量(capacity),当用户滚动条触顶新增可视窗口,类似于懒加载,但由于不是异步操作,速度很快,用户基本没有感觉

问题2

  • 方案1:加一个滚动锁定开关,打开时,锁定滚动条,用户不能操作滚动条,只能看实时输出的日志,关闭时,用户可以像上滚动,日志更新时滚动条不自动沉底
  • 方案2:滚动锁定对用户透明,当运行时分成两个状态,查看状态和实时滚动状态,开始运行默认实时滚动状态,实时刷日志,当用户尝试向上滚动scrollbar时,取消滚动锁定,切换到查看状态,并进行暗箱操作(即output在更新,但不通知组件更新),用户滚动查看已有日志时并不关心有没有新日志输出,当用户将滚动条人工触底时再切回实时滚动状态,用户看到的是最新的日志和滚动更新的效果了(这里也可以是增加下方的滑动窗口看已生成的日志,通过按钮沉到最低,得看具体需求)

问题3

  • 方案1:根据cache当前日志量实时调整下一次addLine的间隔(上方代码做法),日志多刷新频率就快
    • 然而settimeout也是有最小值的,也不是你想多快就多快的
    • 组件刷新速度也会影响下一次settimeout的操作,就是说如果回流导致页面更新太慢,那么你设置的间隔只会是一个摆设
  • 方案2: 只滚最新的n条,这个n可以自己设置,剩下cache.length-n条就直接扔到output里面了,下一次addLine开始再滚最近的n条

design

图片是所有问题中方案2的描述

关键性技术

element.scrollIntoView(alignToTop);

  • 将滚动条滚至使该元素处于视口top或bottom的位置,取决于传参,默认为true
  • 兼容性IE8+
  • 在滚动条触顶,增加新的tick后,需要使滑动条滚至之前触顶时的log位置
var index = this.logWindow[this.logWindow.length - 1].index - this.capacity*this.tick;
if (index > 0 && this.logWindow.length < this.cache.length) {
    this.tick++;
    Vue.nextTick(() => {
        document.getElementById('log-line-'+index).scrollIntoView();
    });
}

触顶触底判断

  • 触顶
e.target.scrollTop === 0
  • 触底
e.target.scrollTop + dom.clientHeight === dom.scrollHeight 

vue黑箱操作

  • vue继承了Array的原型,然后复写了变异方法,所以直接用数组调用方法会触发侦听,使用Array.prototype.someFunc.apply()不会触发侦听
if (silent) {
    [].push.apply(output, intercept);
} else {
    output = output.concat(intercept);
}

javascript基础(3.2)—— 操作符

拾遗

一元操作符

  • ++ -- + -(作为一元操作符)
    • ++和--先Number()后再加1或者减1
    • +等同于Number()
    • -等同于Number()后取负数

位操作符

  • 所有数值以64位格式存储,但位操作符并不直接操作64位的值。而是先将64位的值转换为32位的整数,再将结果转为64位,对于开发人员来说,整个过程就像只存在32位整数一样
  • NaN,Infinity,-Infinity在参与位操作时视为0
  • 按位非(~)
    • ~25 = -26 可以想像当25的二进制全部取反后,这个值是负数,这个数代表的值是这个数取反加1(26)加个负号等于-26
    • ~~(x) 实现了让x取整
  • 按位与(&)
  • 按位或(|)
  • 按位异或(^)
  • 左移(<<),移的位数为5,相当于原数乘以2的5次方
  • 右移(>>)
  • 无符号右移(>>>)符号位也发生移动,用0填充空位

int32 和 Uint32

  • int32范围从-pow(2, 31)到pow(2, 31) -1
    • 大部分的位操作的返回值结果为int32,只有无符号右移是Uint32
  • Uint32范围从0到pow(2, 32)-1,无符号整数其实用的地方较少
    • 左移(<<)和右移(>>)操作会将第二个操作数做ToUint32(),然后 & 0x1F(相当于除以32取余),再向左(右)移动这么多位,结果是int32(参看标准
    • 无符号右移(>>>)会将两个操作数都做ToUint32(),然后第二个操作数 & 0x1F(相当于除以32取余),再向右移动这么多位,结果是Uint32(参看标准
    • 数组取length时都会进行ToUint32(len)的操作,数组最大长度即为Uint32的最大值,pow(2, 32)-1

位操作符的ToInt32(Signed 32 Bit Integer)和ToUint32(Unsigned 32 Bit Integer)

  • 参数为NaN,Infinity,-Infinity,+0,-0时,结果为0,解释了位操作中这些值为什么视为0
  • 其实就是强行化为无长度限制的2进制,然后取最低位32位(模2的32次方)转化为int32或者Uint32

小技巧:按位非,按位与,按位或,按位异或,左移,右移都是对有符号整数的运算,var num = parseInt(str, 2)可以将一个二进制的字符串(比如str为'10101010001000101110101000101110',一定要为32位,可以自己补全)转为一个无符号整数2854414894,可以使用num | 0或者~~num或者 num >> 0等操作实现将num转为一个有符号整数-1440552402

// 都作为有符号整数来转化
// parseInt会保留符号,parseInt('-110', 2) => -6,不会通过第一个bit为来判断符号
var stringToInt32 = function(str){
    // 默认str已经为32位
    return parseInt(str, 2) | 0;
}

// toString(radix)会保留符号,所以(-2).toString(2) => '-10',而不是真正的int32的二进制表示
// 通过先转为Uint32再toString(2)是个不错的选择
var int32ToString = function(num){
    // 保证返回的str是32位的
    var zeroes32 = '00000000000000000000000000000000';
    return (zeroes32 + (num >>> 0).toString(2)).slice(-32);
}

布尔操作符

  • 不能在逻辑与操作中使用未定义的值
if(aa){} // 会报错,aa没有定义
if(this.aa){} //不会报错,视为当前对象没有aa这个属性,当成undefined处理
  • 逻辑非的操作,会先将操作数通过Boolean()函数转为布尔类型,再取反

加性操作符

  • GetValue()后,操作数都会进行ToPrimitive()操作,然后再判断是否有String类型的操作数做字符串拼接,若没有,则再将操作数做ToNumber()操作(主要为了Boolean类型,null,undefined这些)最后再相加
  • 如果有一个操作数是字符串,那么将另一个操作数转化为字符串再进行拼接

减性操作符,乘性操作符,取余操作符

  • 非Number类型的操作数都先通过ToNumber()转为数值,再进行相关操作
  • 减和乘的符号取决于操作数,只有一个负号则为负
  • 取余的符号取决于被除数
    • 被除数为Infinity,或者除数为0,结果为NaN
    • 除数为Infinity,被除数为有限的数,则结果等同于为被除数
    • 被除数为0,除数不为0,则结果等同于被除数0
Infinity * 0; // NaN
Infinity / Infinity; // NaN

关系操作符

  • ==双等号,红书说的不够详尽还有点错误,标准大法好,特别注意在两者的类型一致时结果等同于===三等号
  • ===三等号要保证完全一致,结果才为true
  • The Strict Equality Comparison Algorithm vs. The SameValue Algorithm
    • 就是 === vs Object.is()
    • 区别在于针对+0, -0, NaN上
+0 === -0 // true
Object.is(+0, -0) // false

NaN === NaN // false
Object.is(NaN, NaN) // true

// 如何识别-0
// 方案1
var isNegZero = function(num) { 
    // The SameValue Algorithm 
    return Object.is(num, -0); // es6
}

// 方案2
var isNegZero = function(num) {
    return num === 0 && 1 / num < 0; // 1/-0为-Infinity
}

// 方案3
var isNegZero = function(num) {
    var obj = {};
    // 默认4个Descriptor都为false
    Object.defineProperty(obj, 'num', {value: -0});
    try {
        // The SameValue Algorithm 
        Object.defineProperty(obj, 'num', {value: num});
    } catch(err) {
        return false;
    }
    return true;
}

逗号操作符

  • 使用逗号操作符可以在一条语句中执行多个操作
var num1=1, num2=2, num3=3;
  • 逗号操作符可以用于赋值,总会返回表达式的最后一项
var num = (5, 1, 3, 4, 0); // num值为0

javascript基础(1)—— JavaScript简介

JavaScript包括

  • ECMAScript
  • DOM
  • BOM

ECMAScript

  • 脚本语言的一种规范,Web浏览器只是ECMAScript 宿主环境之一,JavaScript实现了ECMAScript,同样实现的也有Adobe ActionScript(Flash)
  • ECMA-262为规范名,版本目前有1,2,3,5,6
    • ECMAScript 1 首版
    • ECMAScript 2 没有新增和修改功能,格式修正,为了与ISO/IEC-16262保持一致
    • ECMAScript 3 修改了字符串处理,错误定义,数值输出。新增了对正则表达式、新控制语句、try-catch异常处理的支持,标志着ECMAScript成为一门真正的语言
    • ECMAScript 4 由于给这门语言带来的跨越太大,被舍弃
    • ECMAScript 5(ECMAScript 3.1)新功能包括原生JSON对象,继承的方法和高级属性定义,包含一种严格模式
    • ECMAScript 6(ECMAScript 2015)ES6增添了许多必要的特性,例如:模块和类,以及一些实用特性,例如Maps、Sets、Promises、生成器(Generators)等。(对es6各浏览器的支持情况可以参看ECMAScript Compatibility Table

DOM

  • DOM把整个页面映射为一个多层节点结构,这些节点又包含着不同类型的数据,开发人员可以控制页面内容和结构
  • DOM标准在其他几种语言中也有,比如下面几种基于XML的语言
    • SVG
    • MathML
    • SMIL(同步多媒体继承语言)
  • DOM分级
    • DOM 0,指IE 4.0和Netscape Navigator 4.0最初支持的DHTML,其实并不存在这个说法
    • DOM 1,分为DOM Core和DOM HTML。其中,DOM核心规定如何映射基于XML的文档结构,简化对文档中人一部分的访问和操作。DOM HTML添加了对HTML的对象和方法
    • DOM 2,DOM视图,DOM事件,DOM样式,DOM遍历和范围
    • DOM 3,DOM Load and Save,验证文档的方法(DOM Validation),扩展DOM核心,开始支持XML 1.0规范

BOM

  • BOM在H5中才拥有JavaScript实现的这部分的相关标准
  • BOM主要提供这些功能
    • 弹出新浏览器
    • 移动,缩放,关闭浏览器窗口
    • 提供浏览器详细信息的navigator对象
    • 提供浏览器所加载页面的详细信息的location对象
    • 提供用户显示器分辨率详细信息的screen对象
    • 对cookies的支持
    • 像XMLHttpRequest和IE的ActiveXObject这样的自定义对象(AJAX)

javascript基础(3.1)—— 基本类型

拾遗

typeof操作符

  • 可能的值
    • "undefined" -- 这个值没有定义
    • "boolean" -- 这个值是布尔值
    • "string" -- 这个值是字符串
    • "number" -- 这个值是数值
    • "object" -- 这个值是对象或者null
    • "function" -- 这个值是函数
    • "symbol" -- 这个值是符号(es6)
  • typeof是操作符不是函数

Primitive 原始类型

A primitive (primitive value, primitive data type) is data that is not an object and has no methods. In JavaScript, there are 6 primitive data types: string, number, boolean, null, undefined, symbol (new in ECMAScript 2015).

All primitives are immutable (cannot be changed).

布尔型

null == undefined; //true
null === undefined; //false
'' == false; //true

var obj = {};
obj == false; //false
var list = [];
list == false; //true
// 见最下面Object类型转换

if(obj){ // true
    // 里面执行
    console.log('yes');
}

if(list){ // true
    // 里面执行
    console.log('yes')
    // list是一个Object对象,所以if(list)时是true。
}

Number类型

  • 浮点数值最高精度是17位小数
  • 不要测试某个特定的浮点数值
var a = 0.1;
var b = 0.2;
a + b == 0.3 // false; a + b 结果等于0.30000000000000004
  • 正无穷和负无穷
    • Number.MAX_VALUE 和 Number.MIN_VALUE 是js保存的最大值和最小值
    • 超过以上范围的会转换为Infinity(正无穷)和-Infinity(负无穷),判断是否有穷可以用isFinite()函数
    • Number.POSITIVE_INFINITY和Number.NEGATIVE_INFINITY可以获取到Infinity和-Infinity
  • NaN
    • Not a Number,用于表示本来要返回数值的操作数未返回数值的情况
    • 任何涉及NaN的操作都会返回NaN,NaN与任何值都不相等,包括它自己(NaN == NaN // false)
  • isNaN(),主要是判定一个值是否能转为确切的数值
    • isNaN()函数,在接收一个值之后,会尝试将这个值转为数值,如果不能转则返回true
    • isNaN()作用于对象时,会首先调用对象valueOf()方法,然后确定该方法返回的值是否可以转换为数值。如果不能,再基于valueOf()的返回值调用toString()方法,再测试返回值
isNaN(NaN); //true 
isNaN(10); //false (10是一个数值)
isNaN("10"); //false("10"可以被转换成数值10)
isNaN("blue"); //true (不能转换成数值) 
isNaN(true); //false (可以被转换成数值1)
isNaN('123uuu'); //true(不能被Number()转化为数值)
isNaN(new Date()); //false (可以转换为百万毫秒的时间)
isNaN(new Date().toString()); //true (转化为string后就不行了)

isNaN(x) == isNaN(Number(x)) 
// true for every value of x, including x == undefined,
// because isNaN(undefined) == true and Number(undefined) returns NaN,
                             // but ...
isNaN() == isNaN(Number())   
// false, because isNaN() == true and Number() == 0
  • Number.isNaN(),es6,判定这个值是不是NaN,所有不是NaN的都是false
Number.isNaN(NaN); //true
Number.isNaN(Number.NaN) //true
Number.isNaN(0/0) //true

Number.isNaN('NaN'); //false,是字符串不是NaN
  • Number.isNaN()的Polyfill(腻子脚本)
Number.isNaN = Number.isNaN || function(value) {
    return typeof value === "number" && isNaN(value);
}

// Or
Number.isNaN = Number.isNaN || function(value) {     
    return value !== value;
}

参考资料:(来自MDN)
isNaN()
Number.isNaN()

  • Number(),等同于一元加操作符
    • 在面对字符串时,必须保证字符串一定是个数字才能被转化,不存在提取数字的情况
Number(true) = 1;
Number(false) = 0;

Number(null) = 0;
Number(undefined) = NaN;

Number({}) = NaN;
Number([]) = 0;
Number('') = 0;
  • parseInt(string, [radix])
    • 在面对字符串时,先忽略前面所有空格,再提取第一个字符是不是数字字符或者负号
    • 在转化为不同进制的时候,加上radix参数避免歧义
parseInt('') = NaN; // 没有能合法提取的
parseInt('22.5') = 22;
parseInt('123uuu') = 123;

String类型

  • toString()函数
    • 有toString()方法的是数值,布尔值,对象,字符串值
    • num.toString(radix)可以按照某个基数转化为字符串,但只对数值有效
var num = 10;
num.toString(); // '10'
num.toString(2); // '1010'
num.toString(8); // '12'
num.toString(16); // 'a'
  • String()函数
    • 如果值有toString()方法,则调用该方法并返回相应结果
    • 如果值是null,返回'null'
    • 如果值是undefined,返回'undefined'

Object类型(*)

Object的每个实例都具有下列属性和方法

  • constructor:保存着用于创建当前对象的函数
  • hasOwnProperty(propertyName):用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在
  • isPrototypeOf(Object):用于检查传入的对象是否是当前对象的原型
  • propertyIsEnumerable(propertyName):用于检查给定的属性是否能够使用for-in语句来枚举
  • toLocaleString():返回对象的字符串表示,该字符与执行环境的地区对应
  • toString():返回对象的字符串表示
  • valueOf():返回对象的字符串、数值或布尔值表示。通常与toString()方法的返回值相同

Object类型转换

Object ToNumber() & ToString()

  • ToNumber() 先对应 valueOf(), ToString()先对应toString()方法
  • obj为普通对象,obj.valueOf()返回obj自身,obj.toString()返回'[object Object]',但是方法都可重新定义
  • ToNumber()操作时,先检验obj.valueOf()是否为原始值
    • 是原始值(primitive value),返回原始值ToNumber()后的结果
    • 不是原始值或者valueOf方法不存在,再检验obj.toString()是否为原始值
      • 是原始值,返回原始值ToString()后的结果
      • 不是原始值或者toString方法不存在,报错TypeError
  • ToString()操作时,先检验obj.toString()是否为原始值,后面基本同于ToNumber()

Object ToPrimitive()

  • 基本同于上面,不过检验到valueOf()或者toString()是原始值时,直接返回该原始值,不再做原始值的类型转换
  • Date类型先走toString()的检验,类似于ToString()
  • 其他对象类型先走valueOf()的检验,类似于ToNumber()
  • 什么时候用到ToPrimitive()
    • == 松弛相等检验一个Object和一个非Object的时候,Object进行ToPrimitive()
      • 松弛相等会先比较双等号两边类型是否相同,相同则结果同于===
      • 类型不同,则Object做ToPrimitive()
    • + 二元操作符的加号,Object做ToPrimitive()
      • + 一元操作符的加号视为ToNumber()
var a = {};
a.valueOf = () => '1';
a.toString = () => 2;

Number(a) // 1 ==> Number(a.valueOf())
+a  //1 同上
a + a // '11' ==> a转原始值先走valueOf() -> a.valueOf() + a.valueOf()
String(a) // '2'==> String(a.toString())

var obj = {};
Number(obj) // NaN ==> a.valueOf()为自身不是原始值 -> a.toString()为'[object Object]'是原始值 -> '[object Object]'转Number为NaN
obj + obj // '[object Object][object Object]'

var b = new Date();
Number(b) // 1499505510372
+b // 1499505510372
b + b // 'Sat Jul 08 2017 17:25:35 GMT+0800 (CST)Sat Jul 08 2017 17:25:35 GMT+0800 (CST)' ==> Date类型转原始值先走toString()
String(b) // 'Sat Jul 08 2017 17:25:35 GMT+0800 (CST)' 

var c = [];
Number(c) // 0 ==> c.valueOf()为自身不是原始值 -> c.toString()为'' -> ''转Number为0
c + c // '' ==> c转原始值为'' -> '' + ''为''

var d = [1,2];
Number(d) // NaN ==> d.valueOf()为自身不是原始值 -> d.toString()为d.join(',')为'1,2' -> '1,2'转Number为NaN
d + d // '1,21,2'

javascript基础(2)—— HTML中使用js

在HTML中使用JavaScript

  • <script>标签中不常用属性(下俩都是在引用外部脚本情况下有效)
    • async:脚本异步获取,同为async的脚本不关心他们的先后顺序
    • defer:脚本延迟获取,同为defer的脚本,先写先获取,先执行
  • <script>标签,引用外部脚本可以是跨域的,在src中注明路径(可以写jsonp)
  • <script>标签,最好放置在</body>的前面
  • <!DOCTYPE html>即是H5的写法

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.