Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript / js #33

Open
magicdawn opened this issue Jul 28, 2015 · 20 comments
Open

JavaScript / js #33

magicdawn opened this issue Jul 28, 2015 · 20 comments

Comments

@magicdawn
Copy link
Owner

magicdawn commented Jul 28, 2015

Some Tricks on JavaScript.

API

@magicdawn
Copy link
Owner Author

magicdawn commented Jul 28, 2015

RegExp tricks

zero-width assertation

char explination example
\b zero width boundary, 匹配边界 /\bon/.test('on the way') => true
\B zero width none boundary, 匹配非边界 /\Bon/.test('on the way') => false
x(?=y) 只有当 x 后面紧跟着 y 时,才匹配 x。 例如,/Jack(?=Sprat)/ 只有在 'Jack' 后面紧跟着 'Sprat' 时,才会匹配它。/Jack(?=Sprat Frost)/ 只有在 'Jack' 后面紧跟着 'Sprat' 或 'Frost' 时,才会匹配它。然而,'Sprat' 或 'Frost' 都不是匹配结果的一部分。
x(?!y) 只有当 x 后面不是紧跟着 y 时,才匹配 x。例如,/\d+(?!\.)/ 只有当一个数字后面没有紧跟着一个小数点时,才会匹配该数字。 /\d+(?!\.)/.exec("3.141") 匹配 141 而不是 3.141。

new RegExp(str) escape

escapeRegExp("All of these should be escaped: \ ^ $ * + ? . ( ) | { } [ ]");

>>> "All of these should be escaped: \\ \^ \$ \* \+ \? \. \( \) \| \{ \} \[ \] "

实例

千分位分隔符

http://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript

function sep(s){
    return s.replace(/\B(?=(\d{3}+(?!\d)))/g,',');
}

@magicdawn
Copy link
Owner Author

Object.defineProperty / Object.create

node里面的 util.inherits

ctor.prototype = Object.create(superCtor.prototype, {
    constructor: {
      value: ctor,
      enumerable: false, for in
      writable: true, // 可以写
      configurable: true // 可以 delete 修改
    }
  });

@magicdawn
Copy link
Owner Author

hover = mouseenter + mouseleave

mouseover / mouseout
mouseenter / mouseleave

@magicdawn
Copy link
Owner Author

magicdawn commented Sep 22, 2015

mouseover v.s mouseenter

  • mouseenter 事件只有在鼠标指针进入被选元素时被触发
  • mouseover 事件在鼠标指针进入任意子元素时也会被触发

http://www.runoob.com/try/try.php?filename=tryjquery_event_mouseenter_mouseover


https://jsfiddle.net/magicdawn/j9Lbs5yv/
https://developer.mozilla.org/en-US/docs/Web/Events/mouseenter
http://www.cnblogs.com/libmw/articles/2600747.html

mouseenter 不冒泡,但是只进入div3, 会先 mouseenter div1, mouseenter div2, 1 -> 2 -> 3
mouseover 冒泡

mouseleave 不冒泡,但是leave3,会先 leave3 -> leave 2 -> leave1
mouseout 冒泡

冒泡: 一个 Event 实例
不冒泡: 多个 Event 实例, 也会有多个 element 触发 mouseenter / mouseleave 事件

@magicdawn
Copy link
Owner Author

magicdawn commented Sep 30, 2015

String

String.prototype.replace

s.replace(RegExp, function(match, <groups>, offset, string){
})

replace reg, fn , 使用 function 来生成要替换成的字符, 参数需要注意
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replace#指定一个函数作为参数

其中分组参数不一定有的
see example:
image

match 都是 \t, offset 分别是 0 1 3 5 其实就是匹配段在原string中的index值

charCodeAt & codePointAt

  • String.fromCharCode / String.fromCodePoint
  • String.prototype.charCodeAt / String.prototype.codePointAt

charCode / codePoint

  • charCode 范围 0 - 0xFFFF, 作为参数超出范围会被截断
  • codePoint 范围 0 - 0x10FFFF
> x = String.fromCharCode(65539)
'\x03'

因为使用的是 UTF-16 的码点, 16 bit 无法表示所有 unicode 字符. 因此 unicode 表后面的不分需要使用两个 16 bit 码点表示 surrogate pairs

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#utf-16_characters_unicode_code_points_and_grapheme_clusters

@magicdawn
Copy link
Owner Author

global variable

var x = 'global';
delete x; // false

global.x = 'global';
delete x; // true

@magicdawn
Copy link
Owner Author

Q & A

Q

  • 什么是闭包
  • 什么是作用域链

我会从博客园汤姆大叔的博客中的解释来回答. see

A

我自己的理解

变量对象 VO & 活动对象

一个执行上下文中会存在

  • 函数形参
  • 函数定义
  • 变量定义

在上下文对象的VO属性包含这些内容

context.VO = {
    // 函数形参

    // 函数定义

    // 变量定义
}

在进入上下文开始执行时, 这些相关的key会存在于 VO中,并初始化成默认值, 并在执行时依次填入的.
而且, 对于已经存在的key, 后续不会影响它的存在

see example

alert(x); // function

var x = 10;
alert(x); // 10

x = 20;

function x() {};

alert(x); // 20

第一个alert是function, 因为VO key 的顺序是 1形参 2函数声明 3变量声明
2比3优先, 于是VO.x = 是指向一个函数声明的, 后续发现有变量叫x, 但是x这个key 已经存在了, 所以不会影响VO.x的引用, 于是第一个alert处, x是个function

作用域

ES5中只有函数能创建一个新的作用域, 执行上下文, 即是代码块. 函数的context

context = {

  // 变量对象
  VO: {
    // 1. 形参
    // 2. 函数声明
    // 3. 变量定义
  },

  this: this值

  "[[scope]]": [
      parentContext.VO,
      ...
      globalContext.VO
   ]
}

可以看到在函数context中存在一个 [[scope]] 属性, 存放的是
[ 父级context.VO, 爷爷级context.VO, ... , globalContext.VO ]

最后函数的 fn.scope = [fnContext.VO].concat(fnContext.[[scope]]) , 就是函数的作用域数组以自己的VO打头, 上溯至父级context的VO, 直至globalContext.VO为止.

变量查找

变量会沿着作用域链, 并且深入原型链, 解析变量名.

VO变量对象并没有原型, 从下例子看出

Object.prototype.x = 10;
var x = 20;

var fn = ()=>{
  alert(x); // 20
};

函数fn的作用域链会是这样
fn.Scope = [
fnContext.VO,
globalContext.VO
] 如果 fnContext.VO 有原型的话, 就会解析到 Object.prototype.x = 10, 结果是20验证了这一点

闭包

闭包是代码块和创建该代码块的上下文中数据的结合。

就是说在JS中闭包是 函数以及此函数所携带的作用域数据的结合.

这里还要注意的是:在ECMAScript中,同一个父上下文中创建的闭包是共用一个[[Scope]]属性的。也就是说,某个闭包对其中[[Scope]]的变量做修改会影响到其他闭包对其变量的读取:

终于可以给for循环做个好好的解释了:

var data = [];

for (var k = 0; k < 3; k++) {
  data[k] = function () {
    alert(k);
  };
}

为什么直接data[k] = function(){ alert(k) }这样做k总是3, 因为闭包 data[0] data[1] data[2]的 [[scope]]属性是一样的, 他们是在同一个上下文中创建出的闭包, 他们

  • data[0].scope = [ data0Context.VO, globalContextVO ]
  • data[1].scope = [ data1Context.VO, globalContextVO ]
  • data[2].scope = [ data2Context.VO, globalContext.VO ]
    可以看到除了第一个是它们自己的VO外, 后面都是一样的, 他们引用的k不存在于他们自己的VO对象中, 由于外面k修改, 他们都跟着变了.

为什么加一层自执行函数可以.

for (var k = 0; k < 3; k++) {
  (function temp(k){
    data[k] = function () {
      alert(k);
    };
  })(k);
}

此时他们的scope属性如下

  • data[0].scope = [ data0Context.VO, tempContext.VO={ k:0}, globalContext.VO ]
  • data[1].scope = [ data0Context.VO, tempContext.VO={ k:1}, globalContext.VO ]
  • data[2].scope = [ data0Context.VO, tempContext.VO={ k:2}, globalContext.VO ]
    可以看到他们的scope chain中引用到k的地方是没有重合的, 后修改for循环中的k是不会影响到他们scope chain中的k值的.

@magicdawn
Copy link
Owner Author

undefined & null

null是一个表示"无"的对象,转为数值时为0;
undefined是一个表示"无"的原始值,转为数值时为NaN。

null

null表示"没有对象",即该处不应该有值

  • 作为函数的参数,表示该函数的参数不是对象。
  • 作为对象原型链的终点。

undefined

undefined表示"缺少值",就是此处应该有一个值,但是还没有定义

  • 变量被声明了,但没有赋值时,就等于undefined。var x;
  • 调用函数时,应该提供的参数没有提供,该参数等于undefined。
  • 对象没有赋值的属性,该属性的值为undefined。o.x
  • 函数没有返回值时,默认返回undefined. (()=>{ // bla })()

@magicdawn
Copy link
Owner Author

Array.prototype.reduce

关于 initialValue

回调函数第一次执行时,previousValue 和 currentValue 可以是一个值,如果 initialValue 在调用 reduce 时被提供,那么第一个 previousValue 等于 initialValue ,并且currentValue 等于数组中的第一个值;如果initialValue 未被提供,那么previousValue 等于数组中的第一个值,currentValue等于数组中的第二个值。

如果数组为空并且没有提供initialValue, 会抛出TypeError 。如果数组仅有一个元素(无论位置如何)并且没有提供initialValue, 或者有提供initialValue但是数组为空,那么此唯一值将被返回并且callback不会被执行。

[0,1,2,3,4].reduce(function(previousValue, currentValue, index, array){
  return previousValue + currentValue;
});

@magicdawn
Copy link
Owner Author

二进制

http://stackoverflow.com/questions/9939760/how-do-i-convert-an-integer-to-binary-in-javascript

const bin = x=>(x >>> 0).toString(2)

bin(100) // '1100100'

http://stackoverflow.com/questions/16155592/negative-numbers-to-binary-string
http://blog.csdn.net/onewalkingman/article/details/3746154

补码:正数的补码与原码相同,负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加1.

(-3 >>> 0).toString(2) // '11111111111111111111111111111101'

// 1. -3的源码为 10000000 00000000 00000000 00000011
// 2. -3的补码为, 除符号位, 取反, 末尾+1
// 2.1 除符号位取反 11111111 11111111 11111111 11111100
// 2.2 末位+1           11111111 11111111 11111111 11111101    => 0xFF FF FF FD

// -1的表示为
// 1.    10000000 00000000 00000000 00000001
// 2.1  11111111 11111111 11111111 11111110
// 2.2  11111111 11111111 11111111 11111111  => 0xFF FF FF FF
// -1表示为 0xFF FF FF FF 所以 `~-1 = 0`

@magicdawn
Copy link
Owner Author

instanceof

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/instanceof

语法

object instanceof constructor

看 constructor.prototype 在不在 object 的原型链(object.__proto__ ...__proto__)上

Q

Function instanceof Object // true

A

这个比较有趣,Function 也是一个函数,那么他的 __proto__ 是指向 Function.prototype 的,即

Function.__proto__ === Function.prototype // true

// 于是 Function 的原型链为
// [Function.prototype Object.protype null]
// 于是问题= 看`constructor.prototype即Object.prototype === null` 在不在 `Function`的原型链上
// 于是
Function instanceof Object

结论

x instanceof Object 总是为true,因为 constructor.prototype 即 Object.prototype = null, 是原型链的顶端,总会成立的。除了 null undefined

不止 Function,还有 String Number 他们也是函数,他们的 __proto__ = Function.prototype

@magicdawn
Copy link
Owner Author

magicdawn commented Apr 24, 2016

Array Like

Q 怎么判断 isArray

A

  1. 我说取 o.constructor.name === 'Array' 面试官不认, 说再想想
  2. 我说取 o instanceof Array 面试官不认, 死认 Object.prototype.toString.call(o) === '[object Array]'
    好吧

1 & 2 都是可以的, 对于 toString.call(o), 有一些好玩的

var toString = Object.prototype.toString;
toSring.call([1, 2, 3]) // [object Array]
toString.call(null) // [object Null]
toString.call(undefined) // [object Undefined]
toString.call(1) // [object Number]

对于 primitive value / null / undefined 都有用

Update

see

> var o = {}
undefined
> o.__proto__ = Array.prototype
[]
> o instanceof Array
true
> o.constructor === Array
true
> typeof o
'object'
> Object.prototype.toString.call(o)
'[object Object]'
> o.push(1)
1
> o
Array { '0': 1, length: 1 }
> o[0]
1
> JSON.stringify(o)
'{"0":1,"length":1}'

夭寿啊~所以还是老实用 Object.prototype.toString / Array.isArray 吧

lodash

https://github.com/lodash/lodash/blob/3.10.1/lodash.src.js#L8646

@magicdawn
Copy link
Owner Author

magicdawn commented May 3, 2016

RegExp

Flags

/g & /y
https://2ality.com/2020/01/regexp-lastindex.html#the-flags-%2Fg-and-%2Fy

g

"g" 标志意味着正则表达式应该测试字符串中所有可能的匹配。

image

regexp.test 使用 ^ 不指定 global 其实一样, 可能在 match 有不同?

  1. 同一个 regex, 进行多次 exec, 如果使用了 global, 则后续 exec 不会从 index = 0 去找
    image
  2. global 不会改变 ^ $ 的效果
    image

m

让开始和结束字符(^ 和 $)工作在多行模式(也就是,^ 和 $ 可以匹配字符串中每一行的开始和结束(行是由 \n 或 \r 分割的),而不只是整个输入字符串的最开始和最末尾处。

.

匹配任意单个字符,但是换行符除外,包括:\n \r \u2028 或 \u2029。

需要注意的是,m 多行(multiline)标志不会改变点号的表现。因此为了匹配多行中的字符集,可使用[^] (当然你不是打算用在旧版本 IE 中),它将会匹配任意字符,包括换行符。

牵出一个 [^] 匹配任意字符, 包含换行符。


/g

  • 在 regexp.exec 中, global 让 regexp 变成 stateful
  • 在 string.replace / string.match, global 代表着全部匹配的意思

/y
https://2ality.com/2020/01/regexp-lastindex.html#flag-%2Fy-(.sticky)

global with no gap
##-#, 只能匹配前两个 #, 是为 sticky

> const re = /#/gy;
undefined
> re.exec('##-#')
[ '#', index: 0, input: '##-#', groups: undefined ]
> re.exec('##-#')
[ '#', index: 1, input: '##-#', groups: undefined ]
> re.exec('##-#')
null
> re.exec('##-#')
[ '#', index: 0, input: '##-#', groups: undefined ]
> re.exec('##-#')
[ '#', index: 1, input: '##-#', groups: undefined ]
> re.exec('##-#')
null
> re.exec('##-#')
[ '#', index: 0, input: '##-#', groups: undefined ]
> re.lastIndex
1
> re.lastIndex
1
> re.exec('##-#')
[ '#', index: 1, input: '##-#', groups: undefined ]
> re.lastIndex
2
> re.exec('##-#')
null
> re.lastIndex
0
> r2 = /#/y
/#/y
> r2.exec('##-#')
[ '#', index: 0, input: '##-#', groups: undefined ]
> r2.exec('##-#')
[ '#', index: 1, input: '##-#', groups: undefined ]
> r2.exec('##-#')
null
> r2.exec('##-#')
[ '#', index: 0, input: '##-#', groups: undefined ]
> r2.exec('##-#')
[ '#', index: 1, input: '##-#', groups: undefined ]
>

@magicdawn
Copy link
Owner Author

magicdawn commented Jun 3, 2016

string 与 byte[] 互转

拿到 ArrayBuffer, 先转成 Uint8Array, 数组
http://stackoverflow.com/questions/17191945/conversion-between-utf-8-arraybuffer-and-string

  1. Uint8Array -> string
String.fromCharCode(...arr); // 即可
  1. atring -> Uint32Array
    原答案中, escape 成纯 ascii, 这样可以放在 Uint8Array 中, max = 2^8 - 1 = 255
    我放在Uint32中, 序列化成 ArrayBuffer, 通过网络发送, 再取回来即 OK

dataurl -> blob

/**
 * https://stackoverflow.com/questions/4998908/convert-data-uri-to-file-then-append-to-formdata/5100158
 */

function dataURItoBlob(dataURI) {
  // convert base64/URLEncoded data component to raw binary data held in a string
  var byteString
  if (dataURI.split(',')[0].indexOf('base64') >= 0)
    byteString = atob(dataURI.split(',')[1])
  else byteString = unescape(dataURI.split(',')[1])

  // separate out the mime component
  var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]

  // write the bytes of the string to a typed array
  var ia = new Uint8Array(byteString.length)
  for (var i = 0; i < byteString.length; i++) {
    ia[i] = byteString.charCodeAt(i)
  }

  return new Blob([ia], { type: mimeString })
}

atob / btoa

https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/atob

  • atob = base64_decode to binary
  • btoa = base64_encode

escape / unescape

escape('abc123');     // "abc123"
escape('äöü');        // "%E4%F6%FC"
escape('ć');          // "%u0107"

// special characters
escape('@*_+-./');    // "@*_+-./"

@magicdawn
Copy link
Owner Author

property descriptor

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#Modifying_a_property

  • configurable 是否可配置, 为 false 时, 不可更改除 writable 外的属性, 不可赋值, 不可 delete, 只能将 writable 改为 false
  • writable: 是否可以重新赋值
  • enumerable: 是否出现在 for-in / Object.keys 中

默认值

  • obj.prop = value: configurable = writable = enumerable = true
  • Object.defineProperty(obj, 'prop', { value: 'value' }): configurable = writable = enumerable = false

@magicdawn
Copy link
Owner Author

magicdawn commented Jan 15, 2018

scrollTop / clientTop / offsetTop

Links

Summary

  • scrollTop 滚动距离
  • clientTop 这个属性测试下来的结果就是border。
  • offsetTop

只读属性。要确定的这两个属性的值,首先得确定元素的offsetParent。offsetParent指的是距该元素最近的position不为static的祖先元素,如果没有则指向body元素。确定了offsetParent,offsetLeft指的是元素左侧偏移offsetParent的距离,同理offsetTop指的是上侧偏移的距离。

clientWidth / offsetWidth / scrollWidth

  • clientWidth 包括 content / padding
  • offsetWidth 包括 content / padding / border / 滚动填
  • scrollWidth 整体尺寸, 上述博客阐述(不包括 margin / border)

@magicdawn
Copy link
Owner Author

magicdawn commented Oct 18, 2019

Object.is

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is

=== 的区别

// +0 -0
+0 === -0 // true
Object.is(+0, -0) // false

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

@magicdawn
Copy link
Owner Author

NaN / Number.NaN / isNaN / Number.isNaN

However, do note the difference between isNaN() and Number.isNaN(): the former will return true if the value is currently NaN, or if it is going to be NaN after it is coerced to a number, while the latter will return true only if the value is currently NaN:

  • Number.isNaN(val) 判断 val 是否是 NaN 这个值
  • isNaN(val) 判断 val is not a number

@magicdawn
Copy link
Owner Author

HTMLScriptElement

async & defer

https://juejin.cn/post/6844904166528139277#heading-12

简单来说
defer: 延后 script 执行, 因为不需要等你执行, html parse 不会 block. 多个 defer 仍然保持执行顺序.
async: 碰到 script 开始下载, 下载好了开始执行. 多个 async script 执行顺序不确定

@magicdawn magicdawn changed the title JS Tricks JavaScript / js Aug 18, 2023
@magicdawn
Copy link
Owner Author

magicdawn commented Oct 7, 2023

main thread

https://developer.mozilla.org/en-US/docs/Glossary/Main_thread

浏览器使用单个线程

  • 执行 js
  • 处理用户事件 (process user events
  • 处理重绘 (paints: perform layout, reflow, etc
  • GC

https://segmentfault.com/a/1190000041729574#item-2-5
这里介绍了, main thread 中的 paint 是在处理需要绘制的信息, 提交给 compositor thread 执行.

@magicdawn magicdawn pinned this issue Oct 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant