唐洋洋的个人博客

\(^o^)/~


  • 首页

  • 分类

  • 归档

  • 标签

手写JSONP(promise封装)

发表于 2017-10-14 | 分类于 JavaScript

前言

  • JSONP以前研究过,最近又有点忘了,写篇本文mark一下,旨在理解记住JSONP的原理及其实现。代码实现用到es6语法,使用promise来封装JSONP方法,本地测试用的自己node搭的服务器,具体代码就不贴了。
  • 一句话阐述下JSONP原理:动态生成一个JavaScript标签,其src由接口url、请求参数、callback函数名拼接而成,利用js标签没有跨域限制的特性实现跨域请求。
  • 有几点需要注意:1.callback函数要绑定在window对象上
    2.服务端返回数据有特定格式要求:callback函数名+’(‘+JSON.stringify(返回数据) +’)’
    3.不支持post,因为js标签本身就是一个get请求
  • 具体代码如下,最后一段是调用函数的示例,这个函数将返回一个promise对象,获取到数据时状态为resolve
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    const jsonp = function (url, data) {
    return new Promise((resolve, reject) => {
    // 初始化url
    let dataString = url.indexOf('?') === -1 ? '?' : '&'
    let callbackName = `jsonpCB_${Date.now()}`
    url += `${dataString}callback=${callbackName}`
    if (data) {
    // 有请求参数,依次添加到url
    for (let k in data) {
    url += `&${k}=${data[k]}`
    }
    }
    let jsNode = document.createElement('script')
    jsNode.src = url
    // 触发callback,触发后删除js标签和绑定在window上的callback
    window[callbackName] = result => {
    delete window[callbackName]
    document.body.removeChild(jsNode)
    if (result) {
    resolve(result)
    } else {
    reject('没有返回数据')
    }
    }
    // js加载异常的情况
    jsNode.addEventListener('error', () => {
    delete window[callbackName]
    document.body.removeChild(jsNode)
    reject('JavaScript资源加载失败')
    }, false)
    // 添加js节点到document上时,开始请求
    document.body.appendChild(jsNode)
    })
    }
    jsonp('http://192.168.0.103:8081/jsonp', {a: 1, b: 'heiheihei'})
    .then(result => { console.log(result) })
    .catch(err => { console.error(err) })

总结

  • 个人感觉JSONP用的情况还是比较少吧,如果已经是需要服务端配合来进行跨域的情况,为什么不直接用CORS呢

使用eslint检查JS代码

发表于 2017-10-11 | 分类于 JavaScript

前言

  • 代码规范一直是开发过程中比较重要的一环,包括命名规范、统一缩进等等,规范整洁的代码可读性高,也便于后期代码维护以及其他开发人员快速熟悉;本篇就讲一下eslint的具体用法。

介绍

  • eslint早在2013年就有了,而我个人第一次接触还是在使用vue-cli搭建项目的时候,默认配置的eslint会根据规则直接报错,刚开始用的时候满屏报错、特别酸爽~~ 但只要习惯就好,使用eslint检查代码会使你的js文件更加健壮好看。

安装配置

  • 首先安装eslint,直接 npm install eslint即可,注意如果你是全局安装的eslint,后面一些eslint相关依赖包也要全局安装。
  • 然后输入 eslint init 开始初始化一个配置文件,过程中会给你很多选项,比如是否使用es6、是否使用jsx语法、配置文件的文件格式等等。我选择的生成js配置文件,所有选项都选好后,生成一个.eslintrc.js配置文件内容大概长这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    "env": {
    "browser": true,
    "es6": true
    },
    "extends": "eslint:recommended",
    "parserOptions": {
    "sourceType": "module"
    },
    "rules": {
    "indent": [
    "error",
    "tab"
    ],
    "linebreak-style": [
    "error",
    "windows"
    ],
    "quotes": [
    "error",
    "double"
    ],
    "semi": [
    "error",
    "never"
    ],
    "no-console": 0
    }
    };
  • 其中最重要的就是rules里面配置的规则,eslint会根据这里是配置的rule规则对代码进行检验,键值分别对应规则名和状态,比如”no-console“是规则名,后面对应的0表示不执行这条规则。extends表示继承自哪个共享的配置文件,env表示检查代码时所属的环境。

  • 具体状态码规则是这样: 0或者“off”表示关闭规则; 1或者warn表示开启规则,使用警告级别的错误; 2或者error表示开启规则,使用错误级别的错误:error (当被触发的时候,程序会报错)
  • 配置完成后,在当前目录新建一个index.js文件,随便输入几行代码,然后在命令行输入eslint index.js。就会对js文件进行代码检查,如果代码报错,命令行会提示你多少行触发了哪条规则的错误,你就可以对代码进行改正,或者修改配置文件中的rule规则。
  • 配置eslint的方法还有其他的,比如写在package.json中:新增一个eslintConfig,将上述配置文件的内容放在下面。但我个人还是喜欢用单独的配置文件,来控制eslint。

集成到webpack

  • 接下来介绍一下如何把eslint集成到webpack中。首先确保webpack已经安装好,然后依次安装eslint-loader、eslint-config-standard、eslint-plugin-html、eslint-plugin-promise、eslint-plugin-standard、eslint-friendly-formatter。注意如果eslint是全局安装的,这些依赖包也需要全局安装,否则报错找不到对应的包。
  • 然后新建一个webpack的配置文件webpack.config.js,我这里只是做尽量最少的配置,具体如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    module.exports = {
    entry: {
    index: "./src/index.js",
    },
    output: {
    path: __dirname + "/dist/",
    filename: "[name].js"
    },
    resolve: {
    extensions: ['.js', '.vue', '.json']
    },
    module: {
    rules: [
    {
    test: /\.js$/,
    exclude: /node_modules/,
    loaders: [
    'eslint-loader'
    ]
    },
    {
    test: /\.vue|\.js$/,
    enforce: 'pre', // 在babel-loader对源码进行编译前进行lint的检查
    include: /src/, // src文件夹下的文件需要被lint
    use: [{
    loader: 'eslint-loader',
    options: {
    formatter: require('eslint-friendly-formatter') // 编译后错误报告格式
    }
    }]
    }
    ]
    }
    }
  • 这时候文件目录结构如下图
    文件结构

  • 这时在src文件夹下新建index.js文件,命令行输入webpack进行代码打包的过程会使用eslint进行检查,如果无误就在dist/js文件夹下生产打包后的代码,如果检查报错则在命令行中打印出错误位置和触犯的规则。

从几个关键词来理解http缓存

发表于 2017-09-27 | 分类于 http

前言

  • 我对于http缓存的认知总是停留在略知一二的状态,毕竟实际工作中用的比较少-_-||
    但作为一个严谨的程序员,这仍是必备的一个知识技能。想了想还是写篇博客来总结下相关的一些知识,从一些关键词来理解http的缓存
强缓存与协商缓存
  • 首先需要知道的是读取http缓存的方式是有两种的:强缓存(本地缓存)和协商缓存(对比缓存),下面讲一下这两种工作方式的区别和特点。
  • 强缓存: 客户端发送请求时,首先访问浏览器的缓存数据库,若得知缓存存在且有效,则直接读取缓存内容返回给客户端,此时返回状态码为200(from cache);而如果发现缓存失效了或不存在,则会去请求服务器得到相关内容信息,此时返回状态码为200,操作示意图如下
    强缓存.png

  • 协商缓存: 客户端发送请求时,如果是第一次请求资源,则直接访问服务器,服务器将相关内容和缓存标示返回给客户端;如果是再次请求资源,客户端将第一次访问时服务器返回的缓存标示发送给服务器,服务器来判断缓存是否可用,如果缓存可用则告诉客户端可以继续使用缓存的内容,此时返回码为304,操作示意图如下
    协商缓存.png

  • 综上可以直观的看出两种方式的区别,最明显的是缓存命中时返回码不同,还有对比缓存的方式:每一次都会去访问服务器,无论缓存命中与否。那么对于浏览器和服务器是如何区分强缓存还是对比缓存,以及如何判断缓存是否有效呢,这就需要那些放在http报文header中的相关字段了,接下来依次讲解他们。ps:两种缓存方式是可以同时存在的,同时存在时,强缓存的优先级更高。

expires

  • 用于定义强缓存的header字段,这是http1.0时的规范,它的值为一个GMT格式时间字符串,如果发送请求的时间在expires之前,那么本地缓存始终有效,否则就会发送请求到服务器来获取资源。但是已经过时了,基本可以忽略。

cache-control

  • http1.1版本出现的header信息,用来定义缓存信息。包括了好几个字段,其中最重要的就是max-age:用于表示缓存多少秒后失效。比如max-age = 31536000,表示缓存时间为365天,从第一次请求后的365天内再次请求都会去读取缓存。这个也是用来控制强制缓存的header字段。
协商缓存相关的header字段
  • 协商缓存的方式总是会去访问服务器来确定缓存是否可用的,所以需要通过某种标识来进行通信,主要包括下面两组header字段(他们都是要成对出现),即第一次请求的响应头带上某个字段(Last-Modified/Etag),则后续请求头则会带上对应的请求字段(If-Modified-Since/If-None-Match),若响应头没有Last-Modified或者Etag字段,则请求头也不会有对应的字段。
Last-Modified / If-Modified-Since
  • 这组值都是时间字符串。具体行为如下:第一次请求时服务器返回的response headers带上Last-Modified字段表示资源最后一次修改的时间,再次请求时浏览器的request headers带上If-Modified-Since这个值和第一次访问返回的Last-Modified值是一样的,然后给到服务器再进行判断缓存是否过期
Etag / If-None-Match
  • 这组字段的值是服务器生成的唯一标示字符串,用法和上面的Last-Modified/If-Modified-Since类似,第一次访问时服务器会返回一个Etag,再次访问浏览器就会带上If-None-Match其值为第一次返回的Etag,给到服务器,服务器进行判断缓存是否可用。ps: Etag和Last-Modified同时使用时,服务器会优先判断Etag,好像是etag的精准级别更高。

结语

  • 大概就这么多了,都是以最基本的概念为主,参考了一些网上的资料,如果有机会的话再深入地研究下~~~
    参考:彻底弄懂HTTP缓存机制及原理

apply与call性能分析

发表于 2017-09-15 | 分类于 JavaScript

apply call 基本概念

  • 稍有一些javascript基础的人应该对这两个函数都不会陌生,这里简单过一下基本概念。这两个方法一般都是用来调用一个函数时改变其内部this指向,换句话说:劫持一个对象的方法,继承另外一个对象的属性并调用。语法上两者唯一的不同就是传参形式:
  • Function.apply(obj,args)方法能接收两个参数:
    obj:这个对象将代替Function类里this对象
    args:这个是数组,它将作为参数传给Function(args–>arguments)
  • Function.call(obj,[param1[,param2[,…[,paramN]]]]) call方法接收多个参数,比如你调用函数时传入三个参数就得这样写: fun.call(obj,arg1,arg2,arg3),在不确定传入参数有多少个时用call来执行就很麻烦。
  • 以前我的理解就是这两个函数区别就是上面这些了,最近我才知道,他们执行起来的速度差异还挺大的。

    apply与call性能对比

  • 这里有个网站:https://jsperf.com/call-apply-segu,执行测试用例,得出的结果是以Ops/sec(每秒操作数)为单位。根据操作系统和浏览器内核版本等不同,最后的结果也不一样,但总体来说可以得到的结论是:call方法永远比apply方法执行速度要快。下面贴两张运算结果的截图:
    Safari浏览器MacOs系统

Chrome浏览器window系统

  • 初步结论是速度和浏览器内核有关,但apply肯定是要比call运行速度更慢的,在chrome上甚至可以看见,结果居然差了一个位数。
  • 所以在一些框架中,为了追求更快的执行速度就是舍弃掉apply方法或者用call来模拟apply方法。比如我们现在框架上就是这样做的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    switch (args.length) {
    case 0:
    g = gen.call(ctx);
    break;
    case 1:
    g = gen.call(ctx, args[0]);
    break;
    case 2:
    g = gen.call(ctx, args[0], args[1]);
    break;
    default:
    g = gen.apply(ctx, args);
    }
  • 这样根据参数的长度,switch/case判断来执行不同的call语句,实在没有case的时候再执行apply方法。虽然这样写看起来代码很累赘,直接用apply就只需要一句话的事,但是如果要追求极致性能还是需要这样做(在angular2的源码中也有类似的部分,其中写了20个case判断参数长度)。

    结语

  • 那是不是说以后都不要用apply方法了呢?当然不是啦~在项目中某些文件,比如启动程序时执行的,用到了apply方法,但理论上这里只执行一次,那这个时候换成call 方法带来的性能提升基本也可以忽略不计了。
  • ES7中有一个关于绑定this的提案:并排的两个冒号(::)组成的函数绑定运算符,它的左边是一个对象,右边是一个函数,通过这个运算符将左边对象作为this绑定到右边函数上面去。这种写法就不用纠结是用call还是apply了,但现在浏览器都不支持,需要用babel转义。

    1
    2
    3
    foo::bar(...arguments);
    // 等同于:
    bar.apply(foo, arguments);
  • 在不确定参数的情况下,如果不考虑性能,代码层面用apply来写会简洁很多。不过你能确认参数长度的时候,最好都用call方法而不用apply。

vue源码解析之--核心部分core-util(二)

发表于 2017-09-15 | 分类于 JavaScript , vue

前言

 在vue源码目录中有个core文件夹,其核心功能比如组件、数据绑定、虚拟dom等等都在里面。core下面又分为了components、global-api、instance、obsever、util、vdom六个部分,这次还是先以util开头,里面也封装了一些工具型的函数并且引用了外层share里面的工具函数。

debug.js error.js

 debug.js里面主要封装的是用于开发阶段的调试相关的方法,比如一些提示警告。error.js里面只封装了一个handleError方法,用于处理错误,可在config里设置专门的errorHandler函数,没有的时候就只是一个warn警告。debug.js里面有一个repeat方法,将传入字符串重复N次。
var repeat = function (str, n) {
var res = ‘’;
while (n) {
if (n % 2 === 1) { res += str; }
if (n > 1) { str += str; }
n >>= 1;
}
return res
};

这样写的时间复杂度大概是二分之一N,lodash里也有相似的方法,不过是用Math.round(n/2)来代替n>>=1,n>>=1就是把N转换成二进制,然后右移一位再首位补0.

env.js

 env.js主要是一些环境相关的东西,待会儿重点说的是nextTick方法。
var inBrowser = typeof window !== ‘undefined’
是否在浏览器中,只要判断window对象是否存在即可。
var UA = inBrowser && window.navigator.userAgent.toLowerCase();

浏览器中通过userAgent,判断浏览器的版本内核。
function isNative (Ctor) {
return typeof Ctor === ‘function’ && /native code/.test(Ctor.toString())
}

isNative 判断某些方法功能是否原生支持的,比如浏览器支持symbol的话,symbol.tostring就会返回function Symbol() { [native code] },再用正则表达式判断是否包含native code即可。
 下面讲一下nextTick方法,这个方法也比较常用,可以在确定数据改变视图改变后再执行某些动作。代码首先定义一个自执行匿名函数的作用域,然后定义了一个nextTickHandler函数用于执行完成异步后需要执行的函数。
function nextTickHandler () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copiesi
}
}

pending是一个标示符,用于确保timerFunc方法不会被重复调用。接下来是根据浏览器支持情况来定义timeFunc方法:
if (typeof Promise !== ‘undefined’ && isNative(Promise)) {
var p = Promise.resolve()
var logError = err => { console.error(err) }
timerFunc = () => {
p.then(nextTickHandler).catch(logError)
if (isIOS) setTimeout(noop)
}
}

如果浏览器支持promise,就用promise.then来定义timerFunc,promise的具体用法这里也不多讲了,然后我看源码上的注释是说:promise.then执行的时间可能不稳定,当他被推入一个微任务队列中时,添加一个空的setTimeout可以强制刷新微任务队列(IOS系统中).
else if (typeof MutationObserver !== ‘undefined’ && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === ‘[object MutationObserverConstructor]’
)) {
// use MutationObserver where native Promise is not available,
// e.g. PhantomJS IE11, iOS7, Android 4.4
var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
}

当promise不被支持,但浏览器支持MutationObserver时。MutationObserver是html5的一个新特性,用于监测dom节点变化。初始化一个MutationObserver的实例observer ,传入的nextTickHandler是作为监测到发生变化后执行的回调函数,然后这里新建一个文本节点,再使用observer .observe()方法将文本节点绑定监听。最后定义的timerFunc就是改变文本节点的值,从而触发回调,这个回调是在文本节点重新渲染后执行的。当以上两个方法都不支持时,就只好用setTImeout来定义timerFunc了。
return function queueNextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, ‘nextTick’)
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
if (!cb && typeof Promise !== ‘undefined’) {
return new Promise((resolve, reject) => {
_resolve = resolve
})
}
}

最后返回的函数其实就是nextTick自身,传入两个参数,一个是异步回调执行的函数,第二个是一个对象作为执行的环境。将传入的函数以 cb.call(ctx)的形式添加到callback中,然后再执行timerFunc。在global.api中,将这里定义的nextTick放在vue的原型上了。
env.js在最后定义了_Set,如果支持set那么就直接用set了,不支持的话,定义了一个_set类,并为其添加has()、add()、clear()方法。其实set的用法也比较简单,浏览器不支持的话自己重新定义一下也不复杂。

mysql-workbench中PK,NN,UQ,BIN,UN,ZF,AI标识简写说明

发表于 2017-09-15 | 分类于 后端 , 备忘录

  • 使用mysql workbench建表时,可以设置字段标识:PK,NN,UQ,BIN,UN,ZF,AI。他们的分别表示的意思是:
    PK:primary key 主键
    NN:not null 非空
    UQ:unique 唯一索引
    BIN:binary 二进制数据(比text更大)
    UN:unsigned 无符号(非负数)
    ZF:zero fill 填充0 例如字段内容是1 int(4), 则内容显示为0001
    AI:auto increment 自增

使用Javascript做算法题(二)Unique-Paths

发表于 2017-09-15 | 分类于 leetcode算法题

题目描述

  • 在一个 m*n 个格子大小的地图上,一个机器人位于左上角,它的终点在右下角,机器人的前进路线只能向右或向下走,求一共有多少种不同的路线走到终点。注:m和n不会超过100。题目地址

一个3*7的示例

解题思路

  • 看完这个题目,我首先联想到了另一个类似的题,就是上n阶台阶,可以跨一步可以跨两步,求一共有多少种方法。解题方法就是去考虑最后一步,最后要嘛从n-1阶,要嘛从n-2阶跨到终点。假设走到n-1阶有m种不同方案,走到n-2阶有n种不同方案,那么走到n阶就有m+n种不同方案。
  • 有点扯远了,回到这个题目。首先想到的是以二维数组来表示这个地图的坐标,机器人的起点在(0,0)终点在(m,n),机器人最后一步肯定从(m-1,n)或(m,n-1)走,那走到(m,n)的路线总数就是走到(m-1,n)的路线总数加上走到(m,n-1)的路线总数。至此,解题思路也就很明显了,首先确定机器人走到最上面的一行和最左边的一列上的任意点,路线数只能为1。然后再根据这些点的路线数,依次往里推,最后得出(m,n)点的路线数。

    具体代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    * @param {number} m
    * @param {number} n
    * @return {number}
    */
    var uniquePaths = function(m, n) {
    if (m == 1 || n == 1) {
    return 1
    }
    var arr = []
    arr[0] = new Array(n).fill(1)
    for (var i = 1; i < m; i++) {
    arr[i] = new Array(n)
    arr[i][0] = 1
    for (var j = 1; j < n; j++) {
    arr[i][j] = arr[i][j-1] + arr[i-1][j]
    }
    }
    return arr[m-1][n-1]
    };

Object属性特性详解

发表于 2017-09-15 | 分类于 JavaScript

前言

 Object是javascript中最原始的对象,也是理解面向对象十分重要的一环。最近看vue源码时,发现object这方面的东西非常多,就在这里深入了解一下相关知识。本篇主要介绍object对象的一些方法,以及属性的特性相关概念。首先介绍对象的属性,属性类型其实分为两种:数据属性和访问器属性,我们平时接触到的一般都是数据属性,下面依次介绍一下。

数据属性

 数据属性包含一个数据值的位置,可以进行数据的读写。然后数据属性有四个描述其行为的特性,这个特性可以理解为属性的属性。
1.[[configurable]]:表示是否能够修改属性的特性,是否能够使用delete删除属性进而重新定义。
2.[[Enumberable]]:表示是否可以被枚举,此特性为true时,对应的属性在for-in循环中才返回。
3.[[Writable]]:是否可写,此特性为true时,才可以定义修改对应属性的数据值
4.[[Value]]:即属性的数据值。
上面几个特性,前三个默认值都是true,Value默认值undefined。随便定义一个变量及其属性,然后调用Object.getOwnPropertyDescriptors()即可显示其属性,以及属性的特性。
示例.jpg

访问器属性

 首先要明白的是访问器属性不包含数据值,包含一对getter和setter函数。读取访问器时调用getter,写入访问器时调用setter。这两个函数默认值都是undefined,只有使用Object.defineProperty()方法对其进行定义。接着介绍一下object的一些方法。

Object.create()

 参数传入一个原型对象,使用指定的原型对象和其属性创建了一个新的对象。

Object.assign()

 将所有可枚举的属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。第一个参数是目标对象,如果有相同键值的属性,源对象的属性会覆盖掉木雕对象属性, 方法只会拷贝源对象自身的并且可枚举的属性到目标对象身上。

Object.defineProperty() Object.defineProperties()

 Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。Object.defineProperty(obj, prop, descriptor),第一个参数是目标对象,第二个参数是指定属性,第三个参数是描述符对象指定特性。在指定描述符时,只能选择数据描述符和存取描述符的其中一种。Object.defineProperties(obj, props)也是用于为一个对象定义属性,但是可以同时指定多个属性。

Object.keys()

 Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用for…in 循环遍历该对象时返回的顺序一致 (两者的主要区别是 一个 for-in 循环还会枚举其原型链上的属性)。

Object.freeze() Object.seal()

 这两个方法是用于限制对象修改权限的,分别有对应的Object.isFrozen()和Object.isSealed()方法判断是否是冻结(密封)对象,两者唯一的区别是,密封对象仍然可以修改已有属性value的值。
 Object.freeze() 方法可以冻结一个对象,冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的特性。也就是说,这个对象永远是不可变的。该方法返回被冻结的对象。
 Object.seal() 方法可以让一个对象密封,并返回被密封后的对象。密封对象是指那些不能添加新的属性,不能删除已有属性,以及不能修改已有属性特性,但可能可以修改已有属性的值的对象。

结语

 暂时总结就这么多,上面的内容大多是从js高程上看的,在线api查的这里,后面有遗漏或不准确的地方再进行修改。最后说一点,上述的object的方法很多都不支持ie8,这也是为什么vue不支持ie8,但如果是在现代高版本的浏览器进行开发时就不用顾虑了。

为什么parseInt(0.0000008)===8?

发表于 2017-09-15 | 分类于 JavaScript , 冷门

前言

  • 最近看到一篇博客: 这些JavaScript编程黑科技,装逼指南,高逼格代码,让你惊叹不已。上面介绍了挺多让人大开眼界,逼格满满的代码。其中有一句就是parseInt(0.0000008) === 8,结果返回的是true,这里我就详细解释下为什么parseInt(0.0000008)的结果等于8。

    从函数本身出发

  • 这个出乎意料的结果似乎和IEEE 754浮点数标准并没有关系,而是parseInt函数本身的原因,先来看几个用parseInt方法将字符串转换为数字的例子:

    1
    2
    3
    4
    5
    6
    parseInt(' 1 2323') // 1
    parseInt(' abc1abc2323') // NaN
    parseInt(' **1abc2323') // NaN
    parseInt(' 1abc2323') // 1
    parseInt(' 12.9999') // 12
    parseInt(" 0x11") // 17
  • 从中可以看出几条规则:
    1.字符串左边的空格会被忽略掉
    2.从左到右依次取数字字符串,如果最左边以非数字开头则返回NaN
    3.取到第一个数字字符后继续,直到取到一个非数字的字符截止(包括小数点),将这个时候得到的数字字符串转换为数字
    4.以’0x’开头的字符串会做特殊处理表示将后续的数字转换为16进制的整数

  • 到这里可能有人问,代码上传入的是数字类型0.0000008,为什么要分析如何转换字符串类型的参数。那是因为调用parseInt方法时,会隐式地将传入的数字类型用toString方法转换为字符串!而将0.0000008调用toString的结果会是”8e-7”,在javascript中,小于0.000001的浮点数会以科学计数法来表示,而这样的字符串参照上面的规则,自然得到的结果就是8了。

    结语

  • 综上所述用parseInt转换数字的时候坑还是有的,使用时要多加注意。而如果用Math的floor或者round方法则可以得到正常的结果0。

  • 参考资料:
    parseInt() doesn’t always correctly convert to integer

使用Javascript做算法题(三)Largest-Number

发表于 2017-09-15 | 分类于 leetcode算法题

题目描述

  • 给出一串非负整数组成的数组,求用这些数字组成的一个最大数字。举个例子:给出数组[3, 30, 34, 5, 9],那么组成的最大数字就是9534330。注:返回结果数字一定相当大,所以用字符串表示返回的结果。题目地址

    解题思路

  • 这道题解法的关键在于排序数组,需要将数组以生成最大数的顺序排列。我刚开始想到的是,首位数字大的数字就往前放,但是要考虑多位数他可能后面几位比较小的情况。最后发现解题思路很简单:设计一个排序函数:比如两个数字为m和n,就比较mn和nm哪个数字更大,然后在数组调用sort()的时候传入它。

    具体代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    /**
    * @param {number[]} nums
    * @return {string}
    */
    var largestNumber = function(nums) {
    let len = nums.length
    if (len === 0) {
    return ""
    }
    if (len === 1) {
    return nums[0].toString()
    }
    let result = ''
    let sortBy = function (a, b) {
    let ab = a.toString() + b
    let ba = b.toString() + a
    return Number(ba) - Number(ab)
    }
    nums.sort(sortBy)
    for (let i =0;i<len;i++){
    result += nums[i]
    }
    while (result.startsWith ('0') && result.length > 1) {
    result = result.substring(1)
    }
    return result
    };
123
TokenYangForever

TokenYangForever

放点随笔文章

25 日志
14 分类
18 标签
© 2017 TokenYangForever
由 Hexo 强力驱动
主题 - NexT.Muse