记录日常看书、看博客小记
DOM2 DOM3 有关属性
检测节点是否相等 isSomeNode isEqualNode
1 | div1 = document.createElement('div') |
获取框架文档对象 contentDocument contentWindow
1 | var iframe = document.getElementById('myIframe') |
获取行间样式遇到 float 要用 styleFloat
1 | myDiv.styleFloat = 'left' |
几个重要样式属性和方法
- cssText
- length
- item(index)
- getPropertyValue(propertyName)
- removeProperty(propertyName)
- setProperty(propertyName, value, priority)
1 | var demo = document.getElementById('demo') |
计算样式 computedStyle
ie9-使用 oDiv.currentStyle
1 | var computedStyle = document.getComputedStyle(oDiv, null) |
对样式表操作
1 | var sheet = null; |
1 | var sheet = document.styleSheets[0] |
——2017/11/24
样式相关
偏移量
offsetHeight = 元素高度 + (可见)水平滚动条高度 + 上边框高度 + 下边框高度;
offsetWidth = 元素宽度 + (可见)垂直滚动条宽度 + 左边框高度 + 右边框高度;
offsetLeft = 元素左边框至包含元素的左内边框之间的像素距离;
offsetTop = 元素上边框至包含元素的上内边框之间的像素距离;
1 | // 想知道某个元素再页面上的偏移量 |
客户区大小
clientWidth = 元素内容区宽度 + 左右内边距宽度;
clientHeight = 元素内容区高度 + 左右内边距高度;
滚动大小
scrollHeight: 在没有滚动条的情况下,元素内容的总高度;
scrollWidth: 在没有滚动条的情况下,元素内容的总宽度;
scrollLeft: 被隐藏在内容区域左侧的像素数。通过设置这个属性可以改变元素的滚动位置。
scrollTop: 被隐藏在内容区域上方的像素数。通过设置这个属性可以改变元素的滚动位置。
PS:在不包含滚动条的页面而言,scrollWidth 与 clientWidth,scrollHeight 与 clientHeight 的关系并不是十分清晰。
- Firefox,这两组属性始终相等,但大小代表的是文档内容区域的实际尺寸,而非视口尺寸;
- Oprea、safari、chrome 中这两组属性有差别,其中 scrollWidth 和 scrollHeight 等于视口大小,而 clientWidth 和 clientHeight 等于文档区域大小;
- IE,这两组属性不相等,scrollHeight 和 scrollWidth 等于文档内容区域大小,而 clientHeight 和 clientWidth 等于视口大小;
所以,我们一般采用获取最大值,保证跨浏览器准确:
1 | var docHeight = Math.max(document.documentElement.scrollHeight, doucument.documentElment.clientHeight); |
html4.0 的 DTD
1 |
html5 的 DTD
1 |
在文档使用了 DTD
时,document.body.scrollHeight
值为 0,没有用 DTD
时不为 0
确定元素大小
getBoundingClientRect()方法,返回一个对象,包括四个属性:left、top、right 和 bottom。这些属性给出了元素相对视口的位置。
——2017/11/26
范围
selectNode() 选择整个节点
selectNodeContents() 只选择节点的子节点
html
1 | <p id='p1'> |
js
1 | var p1 = document.getElementById('p1'), |
1 | range.deleteContents() // 删除范围选区 |
事件
为了兼容所有浏览器,一般对元素添加、删除事件做如下处理(不过一般 IE9+都没有必要这么做)
1 | var EventUtil = { |
扫盲:
以前认为在页面卸载的时候没有办法去控制,当初没有注意到 window 下的 beforeunload 事件
1 | EventUtil.addHandler(window, 'beforeunload', function (ev) { |
新认识一个事件,DOMContentLoaded事件在形成完整的 DOM 数之后就触发,不会理会图片、JavaScript 文件、css 文件或其他资源是否已经下载完毕。
—— 2017/11/27
自定义事件
1 | EventUtil.addHandler(selfBtn, 'myEvent', function (ev) { |
—— 2017/12/6
表单
form 表单作为一种古老的数据提交方式,很多细节还真是头回见,下面小记下。
1 | <form action="http://xxx.com" method="post" id="form1"> |
1 | var forms = document.forms // 获取页面中所有form集合 |
单击一下代码生成的按钮,可以提交表单
1 | <input type="submit" value="Submit form"> |
这种方式提交表单,浏览器会将请求发送到服务器之前触发 submit 事件。
1 | var form = document.querySelector("form"); |
除了
值得注意的是,对 value 属性所做的修改,不一定会反映在 DOM 中,因此,在处理文本框的值时,最好不要使用 DOM 方法。
为解决不知道用户选择了什么文本的困扰,新认识了两个属性:selectionStart、selectionEnd。
1 | $name.addEventListener("select", function(ev) { |
设置选中部分文本解决方案:setSelectionRange
1 | $name.value = "hello form"; |
复制&&粘贴问题解决方案:event.clipboardData/window.clipboardData 获取到 clipboardData 对象,有 setData 和 getData 方法。只有 opera 不支持。Firefox、safari 和 chrome 只允许在 paste 事件发生时读取剪贴板数据,而 ie 没有这个限制。
以前对 select 的操作过于依赖 jQuery 或者 DOM 操作,其实本身有些很好的方法和属性。
HTMLSelectElement 提供的一些属性和方法:
- add(newOption, relOption):向控件中插入新
- multiple:是否允许多项选择。
- options:控件中所有
- remove(index):移除给定位置的选项。
- selectedIndex:基于 0 的选中项索引,没有选中项,返回-1.对于多选项,只返回选中项中的第一项索引。
- size:选择框中可见行数。
HTMLOptionElement 有一下属性:
- index:当前选项在 options 集合中的索引。
- label:当前选项的标签。
- selected:当前选项是否被选中。将这个属性设置位 true 可以选中当前选项。
- text:选项的文本。
- value:选项的值。
1 | <select name="is-student" id="is-student"> |
1 | options = $isStudent.options; |
—— 2017/12/8
typeof undefined
以前总迷惑,为嘛能够直接
1 | if(aaa === undefined) |
看到别人偏偏
1 | if(typeof aaa == "undefined") |
今天才明白其中道理:因为在 js 中 undefined 可以被重写,这样防止页面中有 undefined 变量存在。下面来看看区别:
1 | ;(function (undefined) { |
作用于安全构造函数
构造函数其实是一个使用 new 操作符调用的函数。当使用 new 调用时,构造函数内用到的 this 对象会指向新创建的对象实例。
1 | function Person(name, age) { |
如果构造函数被当作普通函数调用,this 就会指向 window 对象,添加成 window 下的属性。
1 | var person = Person('wuwh', '22') |
解决这个问题的方法时创建一个作用域安全的构造函数,原理是在进行任何更改前,确认 this 对象是指向正确的实例。
1 | function Person(name, age) { |
—— 2017/12/9
HTML5 原生 API
XDM
跨文档消息传送(XDM),HTML5 原生提供了 postMessage 方法。
postMessage()方法接收两个参数:
- 一条消息
- 一个表示消息接收方来自哪个域下的字符串
1 | var frameWindow = document.querySelector('iframe').contentWindow |
接收到 XDM 消息时,会触发 window 对象的 message 事件,改事件会包含三个重要信息:
- data:postMessage()第一个参数;
- origin:发送消息的文档所在的域;
- source:发送消息的文档 window 对象的代理,用于发送上一条消息的窗口中调用 postMessage()。
1 | // 接收XDM消息 |
拖放事件
在被拖动元素上依次触发事件:
- dragstart
- drag
- dragend
在防止目标上依次触发事件:
- dragenter
- dragover
- dragleave
- drop
为了阻止默认行为,一般都要对 dragenter、dragover 和 drop 绑定阻止默认事件。
认识一个新的事件属性 dataTransfer,用于从被拖放元素向放置目标传递字符串格式的数据。
1 | // 设置文本和url数据 |
—— 2017/12/9
高级函数
惰性载入函数
有时候对浏览器的检测,我们执行一次就行,不必每次调用进行分支检测。解决方案就是惰性载入。
- 在第一次调用过程中,该函数被覆盖为另一个合适方式执行的函。
1 | function createXHR() { |
- 函数声明时就自执行指定恰当的函数。
1 | var createXHR = (function () { |
函数绑定
指定一个函数内 this 环境,ES5 原生可以用 bind,bind 实现原理时这样的:
1 | function bind(fn, context) { |
bind 一般用于事件处理程序以及 setTimeout()和 setInterval()。因为这些直接用函数名,函数体内 this 时分别指向元素和 window 的。
函数柯里化
上面模拟绑定函数的实现,发现不能传参。于是,对绑定函数进行传参处理叫做函数柯里化。
实现可以传参的 bind 函数。
1 | function bind(fn, context) { |
防止篡改对象
Object.preventExtensions() 防止给对象添加新属性和方法。
1 | var person = { |
Object.seal() 防止删除对象属性和方法。
1 | var person = { |
Object.freeze() 冻结对象,既不可以拓展,也不可以密封,还不可以修改。
1 | var person = { |
定时器
理解这段话就明白为什么 setInterval 要谨慎使用了。
使用 setInterval()创建的定时器确保了定时器代码规则地插入到队列中。问题在于,定时器代码可能在被添加到队列之前还没有完成执行,结果导致定时器代码运行好几次,而之间没有停顿。在这里 js 引擎避免了这个问题。当时用 setInterval()时,仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中。确保了定时器代码加入到队列地最小时间间隔为指定间隔。
造成后果:(1)某些间隔被跳过;(2)多个定时器地代码执行之间地间隔可能会比预期地小。
—— 2017/12/13
1 | /**自定义事件基于观察者设计模式 |
—— 2017/12/14
ES6 之 Symbol
Symbol 是 ES6 中引入的一个第七种数据类型(前六种分别是 undefined、null、Boolean、String、Number、Object)。目的是使得属于 Symbol 类型的属性都是独一无二的,可以保证不与其他属性名产生冲突。
Symbol 函数相同入参,返回值不相等
1 | let sym1 = Symbol('my symbol') |
Symbol 值不能和其他类型的值进行运算,包括自身。但是可以显示转化成字符串,也可以转化成布尔值
1 | let sym = Symbol('my symbol') |
Symbol 值作为对象属性
1 | let mySymbol = Symbol() |
获取对象所有 Symbol 属性名
1 | const obj = {} |
Symbol.for() 搜索返回已有参数名称的 Symbol 值,没有则会新建以改字符串为名称的 Symbol 值
1 | let s1 = Symbol.for('foo') |
Symbol.for 登记的名字是全局环境的
1 | let iframe = document.createElement('iframe') |
ES6 之 Proxy
Proxy 属于一种“元编程”,即对编程语言进行编程。可以理解成在木匾对象之前架设一层“拦截”
1 | let proxy = new Proxy( |
Proxy 实例可以作为其他对象的原型对象
1 | let proxy = new Proxy( |
Proxy 的一些实例方法
1 | let handler = { |
writable 和 configurable 属性都为 false 时,则该属性不能被代理,通过 Proxy 对象访问该属性会报错
1 | let obj = {} |
—— 2017/12/15
ES6 之 Reflect
Reflect 对象与 Proxy 对象一样,也是 ES6 为了操作对象提供的新 API。
Reflect 对象一共有 13 个静态方法。
- Reflect.apply(target, thisArg, args)
- Reflect.construct(target, args)
- Reflect.get(target, name, receiver)
- Reflect.set(target, name, value, receiver)
- Reflect.defineProperty(target, name, desc)
- Reflect.deleteProperty(target, name)
- Reflect.has(target, name)
- Reflect.ownKeys(target)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.getOwnPropertyDescriptor(target, name)
- Reflect.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, prototype)
Reflect.get && Reflect.set
在 name 属性部署了读取函数(getter)或者是设置函数(setter),this 绑定 receiver
1 | var obj = { |
如果 Proxy 对象和 Reflect 对象联合使用,前者拦截赋值操作,后者完成赋值的默认行为,而且传入 receiver,那么 Reflect.set 会触发 Proxy.defineProperty
1 | var obj = { |
Reflect.constructor(target, args)
1 | function Geeting(name) { |
Reflect.getPrototypeOf(obj) && Reflect.setPrototypeOf(obj, newProto)
设置和读取对象的proto属性
1 | function FancyThing() {} |
Reflect.ownKeys
1 | var obj = { |
ES6 之 Set 和 Map
set
Set 是 ES6 新数据结构,类似于数组,但是成员都是唯一的,没有重复的值
1 | var s = new Set() |
可以看成是一种数组的去重方法 变量解构
1 | const set = new Set([1, 2, 3, 4, 1, 2, 3]); |
两个对象被视为不相等
1 | let set1 = new Set([{}, {}]) |
Set 的方法 add、delete、clear 和 has
1 | let s = new Set([0, 1]) |
可以看成是一种数组的去重方法 Array.from
1 | const set = new Set([1, 2, 3, 4, 1, 2, 3]) |
实现并集,交集和差集
1 | let a = new Set([1, 2, 3]) |
1 | // 垃圾回收机制依赖引用计数,如果一个值的引用次数不为0,垃圾回收机制就不会释放这块内存。 |
Map 数据结构类似对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值都可以
1 | let m = new Map() |
Map 可以接收一个数组作为参数,数组成员是一个个表示键值对的数组
1 | let m = new Map([ |
事实上不仅仅是数组,任何具有 Iterator 接口、 每个成员都是一个双元素的数组,都可以当作 Map 构造函数的参数
1 | let set = new Set([ |
一个键值多次赋值,后面的会覆盖前面的
1 | let m = new Map() |
Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键
1 | let m = new Map() |
forEach 方法接受第二个参数,用来绑定 this
1 | let reporter = { |
—— 2017/12/18
ES6 之 Promise
今天复习一下 ES6 中 Promise 的基础用法。ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。Promise 对象有两个特点:
- 对象的状态不受外界影响;
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果;
优点:
- 就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
- Promise 对象提供统一的接口,使得控制异步操作更加容易。
缺点:
- 无法取消 Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
- 当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
Promise 新建后立即执行,所以首先输出的是 Promise。然后,then 方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以 resolved 最后输出。
1 | let promise = new Promise(function (resolve, reject) { |
Promise 实现 ajax
1 | const getJSON = function (url) { |
第一个回调函数完成以后, 会将返回结果作为参数, 传入第二个回调函数。
1 | getJSON('js/data.json') |
前一个回调函数,有可能返回的还是一个 Promise 对象,这时后一个回调函数,
就会等待该 promise 对象的状态发生变化,才会被调用,否则不会被调用。
1 | getJSON('js/data.json') |
resolve 语句后,抛出错误,不会被捕获,等于没有抛出,Promise 状态一旦改变,不会再改变。
1 | const promise = new Promise(function (resolve, reject) { |
catch、then 中抛出的错误都会一级一级往后冒泡,直到被后面的 catch 捕获到。
1 | const promise = function () { |
p1 和 p2 都是 Promise 的实例,但是 p2 的 resolve 方法将 p1 作为参数,这时 p1 的状态就会传递给 p2,也就是说,p1 的状态决定了 p2 的状态
1 | const p1 = new Promise(function (resolve, reject) { |
—— 2017/12/21
立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务
1 | new Promise((resolve, reject) => { |
所有 Promise 实例的状态都变成 fulfilled,Promise.all 状态才会变成 fulfiled
只要有一个别被 rejected,Promise.all 状态就变成 rejected
1 | let getJSON = function (url) { |
其中一个实例状态率先发生改变,Promise.race 的状态就跟着改变,这个率先改变实例的返回值作为回调入参
1 | Promise.race([fetch('data/data1.json'), fetch('data/data2.json')]) |
立即 resolve 得 Promise 对象,是本轮“事件循环”得结束时,而不是下一轮“事件循环”的开始
1 | setTimeout(() => { |
Promise.reject()方法的参数,会原封不动地作为 reject 的理由,变成后续方法的参数
1 | const thenable = { |
捕获最后抛出来的错误
1 | Promise.prototype.done = function (fulfiled, rejected) { |
—— 2017/12/22
ES6 之 Iterator 和 for…of 循环
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
模拟 next 方法
1 | var it = makeIterator(['a', 'b']) |
解构、拓展运算符都会默认调用 iterator 接口
覆盖原生遍历器
1 | let str = new String('hi') |
yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
1 | let generator = function* () { |
一个数据结构只要部署了 Symbol.iterator 属性,就被视为具有 iterator 接口,就可以用
for…of 循环遍历它的成员.也就是说,for…of 循环内部调用的是数据结构的 Symbol.iterator 方法
for…of 循环可以使用的范围包括数组,Set 和 Map 结构,某些类型的数组的对象(arguments 对象,DOM NodeList 对象)
Generator 对象以及字符串
DOM NodeList 对象部署了 iterator 接口
1 | let ps = document.querySelectorAll('p') |
for…of 能正确识别 32 位 UTF-16 字符
1 | for (let x of 'a\uD83D\uDC0A') { |
并不是所有类似数组的对象都具有 iterator 接口
1 | let arrayLike = { |
forEach 缺点:break 或 return 不奏效
1 | let arr = [1, 2, 3]; |
—— 2017/12/25
ES6 之 Generator
Generator 函数调用并不执行,返回的也不是函数运行的结果,而是一个指向内部状态的指针对象,也就是遍历器对象。
1 | function* helloWorldGenerator() { |
yield 表达式只能用在 Generator 函数里面,用在其他地方都会报错
yield 表达式在另个一表达式中,必须放在圆括号里面。放在函数参数或放到赋值表达式的右边,可以不加括号。
1 | function foo() {} |
任意一个对象的 Symbol.iterator 方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。
由于 Generator 函数就是遍历器生成函数,依次可以把 Generator 赋值给对象的 Symbol.iterator,从而使得该对象具有 Interator 接口。
1 | let myIterable = {} |
Generator 函数执行后,返回一个遍历器对象。该对象本身也具有 Symbol.iterator 属性,执行后返回自身。
1 | function* gen() {} |
yield 表达式本身没有返回值,或者说总是返回 undefined。next 方法可以带一个参数,该参数就会被当作上一个 yield 表达式的返回值。
1 | function* foo(x) { |
遍历斐波拉契数列
1 | function* fibonacci(large) { |
原生对象没有 iterator 接口,无法用 for…of 遍历,可以通过 Generator 函数加上遍历接口。
1 | function* objectEntries(obj) { |
扩展运算符、解构赋值和 Array.from 方法内部调用都是遍历器接口。
1 | function* numbers() { |
在 Generator 函数内部,调用另一个 Generator 函数,默认情况下是没有效果的。
yield* 后面的 Generator 函数(没有 return 语句时),等同于在 Generator 内部部署了一个 for…of 函数。
1 | function* foo() { |
被代理的 Generator 函数有 return 语句,那么就可以向代理它的 Generator 函数返回数据。
1 | function* foo() { |
将 Generator 函数内部 this 指向它的原型上,可以 new 命令。
1 | function* gen() { |
return 方法返回给定的值,并且终结遍历 Generator 函数。
1 | function* gen() { |
Generator 函数内部没有部署 try…catch,那么 throw 抛出的错误,被外部 try…catch 捕获。
Generator 函数内部和外部,都没有部署 try…catch,程序将会报错,中断执行。
1 | function* gen() { |
next()、throw()、return()这三个方法本质时同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换 yield 表达式。
1 | function* gen(x, y) { |
—— 2017/12/26
ES6 之 Generator 函数的异步应用
对于多个异步操作,要等到上一个操作完才执行下一个,这时候就需要封装一个,Generator 函数自动执行器。
1 | function run(fn) { |
回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
Promise 对象。将异步操作包装成 Promise 对象,用 then 方法交回执行权。
1 | // co函数源码 |
—— 2017/12/27
1 | class Point { |
类的数据类型就是函数
1 | console.log(typeof Point); // function |
类本身就指向构造函数
1 | console.log(Point === Point.prototype.constructor); // true |
直接对类使用 new 命令
1 | let p = new Point(1, 2); |
x 和 y 都是对象 point 自身的属性(定义在 this 变量上),toString 是原型对象的属性(定义在 Point 类上)
实例上调用的方法,就是调用原型上的方法
1 | console.log(p.toString === Point.prototype.toString); // true |
给实例的原型上添加方法
1 | Reflect.getPrototypeOf(p).getX = function() { |
类的属性名,可以采用表达式
1 | let methodName = "getArea"; |
类中没有定义 constructor 方法,js 引擎会自动为它添加一个空的 constructor 方法,constructor 方法默认返回实例对象,也可以指定返回另一个对象
1 | class Foo { |
用表达式表示一个类,类的名称是 MyClass,Me 只在 Class 内部代码可用,指代当前类,如果内部没有使用到的话,可以省略 Me
1 | const MyClass = class Me { |
在类的内部使用 get 和 set 关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为
1 | inst.prop = 123; // setter:123 |
for…of 循环自动调用遍历器
1 | class Foo { |
所有类中定义的方法,都会被实例继承,如果在一个方法前,加上 static 关键字,就表示该方法不会被实例继承,而是直接通过类来调用,成为“静态方法”。
静态方法中的 this 指向 Foo 类,而不是实例。静态方法可以与非静态方法重名
1 | console.log(Foo.sayHi()); // hi |
父类的静态方法可以被子类继承
1 | console.log(Bar.sayHi()); // hi |
静态方法可以从 super 对象上调用
1 | console.log(Bar.childSayHi()); // hi child |
子类继承父类时,new.target 会返回子类
1 | console.log(new Bar()); // false |
ES6 之 Class 的继承
子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错,如果子类没有定义 constructor 方法,这个方法会被默认添加。在子类构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则报错。
1 | class ColorPaint extends Point { |
super 虽然代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即 super 内部 this 指的是 B。
1 | class A { |
super 作为对象时,在普通方法中,指向父类的原型对象;在静态方法中指向父类。
1 | class A { |
ES6 规定,通过调用父类方法时,方法内部的 this 指向子类。
1 | class A { |
如果 super 作为对象,用在静态方法中,这时 super 将指向父类,而不是父类原型对象。
1 | class Parent { |
1 | class A {} |
A 作为一个基类,就是一个普通函数,所以直接继承 Funtion.prototype,A 调用后返回一个空对象,所以,A.prototype.proto指向构造函数的 prototype 属性。
1 | class A {} |
原生构造函数可以被继承
1 | class VersionedArray extends Array { |
ES6 之 Module
export
通常情况下,export 输出的变量就是本来的名字,但是也可以使用 as 关键字重命名。
1 | function v1() {} |
export 命令规定是对外接口,必须与模块内部变量建立一一对应关系。
1 | // 变量写法一 |
export 语句输出的接口,与其对应的值是动态绑定关系
1 | export var foo = 'bar' |
import
import 命令具有提升效果,会提升到整个模块的头部,首先执行。
1 | foo() |
目前阶段,通过 Babel 转码,CommonJS 模块的 require 命令和 ES6 模块的 import 命令,可以写在同一个模块里面,但是最好不要这样做。因为 import 在静态解析阶段执行,所以它是一个模块之中最早执行的。
注意,模块整体加载所在的那个对象(上例是 circle),应该是可以静态分析的,所以不允许运行时改变。下面的写法都是不允许的。
1 | import * as circle from './circle' |
export default 命令为模块指定默认输出。其他模块加载该模块时,import 命令可以为该匿名函数指定任意名字。
第一组是使用 export default 时,对应的 import 语句不需要使用大括号;第二组是不使用 export default 时,对应的 import 语句需要使用大括号。
1 | // 第一组 |
—— 2017/12/28
ES6 之 Module 加载
ES6 模块与 CommonJS 模块之间的差异:
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
ES6 之编程风格
- 在 let 和 const 之间,建议优先使用 const,尤其是在全局环境,不应该设置变量,只应设置常量。
- 所有的函数都应该设置为常量。
- 静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
- 箭头函数取代 Function.prototype.bind,不应再用 self/_this/that 绑定 this。
- 注意区分 Object 和 Map,只有模拟现实世界的实体对象时,才使用 Object。如果只是需要 key: value 的数据结构,使用 Map 结构。因为 Map 有内建的遍历机制。
- 如果模块只有一个输出值,就使用 export default,如果模块有多个输出值,就不使用 export default,export default 与普通的 export 不要同时使用。
—— 2017/12/28
ES6 之数组
复制数组
1 | const a1 = [1, 2] |
拓展运算符值会部署了 iterator 接口的对象转化为数组,包括字符串、Set、Map、generator 函数、数组、NodeList 等
类似数组的对象(array-like object)和可遍历(iterable)的对象可用 Array.from 方法转化
1 | let arrayLike = { |
Array.from 还可以接受第二个参数,作用类似于数组的 map 方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
1 | console.log(Array.from(arrayLike, (x) => x.repeat(2))) |
Array.of 方法用于将一组值,转化为数组
1 | console.log(Array.of(3, 10, 9)) |
将指定位置的成员复制到其他位置
1 | console.log([1, 2, 3, 4, 5].copyWithin(0, 3, 4)) // [4, 2, 3, 4, 5] |
find 找出第一个符合条件数组成员,
findIndex 找出第一个符合条件数组成员索引
1 | let f = [1, 3, 5, 7].find((n) => n > 3) |
fill 填充数组
1 | console.log(new Array(3).fill(6)) |
fill 方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
1 | console.log([1, 2, 3, 4].fill('a', 1, 4)) // [1, "a", "a", "a"] |
include 表示某个数组是否包含给定的值第二个参数表示搜索的起始位置
1 | console.log([1, 2, 3, NaN].includes(NaN)) //true |
数组空位相关
1 | // 数组空位是没有任何值的 |
ES6 之 String
codePointAt 方法在第一个字符上,正确地识别了“𠮷”,返回了它的十进制码点 134071(即十六进制的 20BB7)。在第二个字符(即“𠮷”的后两个字节)和第三个字符“a”上,codePointAt 方法的结果与 charCodeAt 方法相同。
1 | let s = '𠮷' |
endsWith 的行为与其他两个方法有所不同,它针对前 n 个字符,而其他两个方法针对从第 n 个位置直到字符串结束。
1 | let str = 'Hello world' |
repeat()
1 | // 小数会被取整 |
padStart() padEnd()
1 | // 头部补全 |
模板字符串里可以嵌套
1 | let $body = document.querySelector('body') |
执行一段字符串
1 | let str = `return ` + '`Hello ${name}`' |
标签模板
1 | function passthru(literals, ...values) { |
tag 函数的第一个参数 strings,有一个 raw 属性,也指向一个数组
1 | tag`abc\nefg` |
—— 2018/1/3
ES6 之 Object
把表达式放到方括号里,作为对象的属性名
1 | let propKey = 'foo' |
把表达式放到方括号里,作为对象下的方法名
1 | let obj = { |
属性名表达式如果是一个对象,默认情况下会自动转化为字符串[object Object]
1 | const propKey = { a: 1 } |
getter 和 setter 函数 name 属性在该方法的属性描述对象的 get 和 set 属性上面
1 | const obj = { |
Function 构造函数创造的函数,name 属性返回 anonymous
1 | console.log(new Function().name) // anonymous |
bind 方法创造的函数,name 属性返回 bound 加上原函数的名字
1 | let doSomething = function () {} |
Object.is() 同值相等 不同于运算符(===),一是+0 不等于-0,二是 NaN 等于自身
1 | console.log(Object.is(+0, -0)) // false |
assign
1 | let a = Object.assign(2) |
ES6 规定,所有 class 的原型方法都是不可枚举的
1 | let cd = Object.getOwnPropertyDescriptor( |
Reflect.ownKeys 遍历对象属性类型顺序 数字 -> 字符串 -> Symbol
1 | console.log(Reflect.ownKeys({ [Symbol()]: 0, a: 1, 0: 2 })) // ["0", "a", Symbol()] |
ES2017 引入了 Object.getOwnPropertyDescriptors 方法,返回指定对象所有自身属性(非继承属性)的描述对象
1 | const obj = { |
getOwnPropertyDescriptors 可应用于将两个对象合并,包括 set 和 get
1 | const shallowMerge = (target, source) => Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) |
对象上部署proto属性,一下三种方法都能达到效果
1 | let prot = {} |
super 关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错,super 等价于 Object.getPrototypeOf(this)
1 | const obj = { |
拓展运算符的解构赋值,不能复制继承自原型对象的属性
1 | let a = { a: 1 }; |
—— 2018/1/4
1 | { |
1 | // 指定默认值后,函数的length属性将失真 |
—— 2018/1/5
ES6 之 function
箭头函数不能当作构造函数,原因在于箭头函数内部没有 this,而是引用外层的 this
1 | let Fn = () => { |
箭头函数不能用作 Generator 函数
1 | let g = function* () => { |
箭头函数没有自己的 this,所以 bind 方法无效,内部的 this 指向外部的 this
1 | let res = function () { |
“尾调用优化”意义:函数执行到最后一步,不保留外层函数的调用帧,只会保存内部函数调用帧,这样节省了内存。注意,只有不再用到外层函数内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则无法进行“尾调用优化”。
1 | function addOne(a) { |
尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身,做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。
1 | // 普通方法递归 |
1 | // 尾递归 |
ES6 之 class 继承(续)
继承 Object 子类,有一个行为差异,ES6 改变了 Object 构造函数的行为,发现不是通过 new Object()形式调用,Object 构造函数忽略参数
1 | class NewObj extends Object { |
将多个类的接口“混入”另一个类
1 | function mix(...mixins) { |
—— 2018/1/8
关于从页面外部加载 js 文件
- 带有 src 属性
<script>
标签之间还包含 JavaScript 代码,则只会下载并执行外部脚本文件,嵌入的代码会被忽略; - 不存在 defer 和 async 属性,浏览器就会按照
<script>
在页面中出现的先后顺序对它们依次进行解析; <script>
有 defer 属性,浏览器会立刻下载,但延时执行(延时到</html>
后执行),HTML5 规定按照文件出现的先后顺序执行,先于 DOMContentLoaded 事件执行;<script>
有 async 属性,浏览器立刻下载,不保证按照先后顺序执行,一定在 load 事件前执行,但不一定在 DOMContentLoaded 之前执行;
重绘 repaint 与重排 reflow
重绘:当改变那些不会影响元素在网页中的位置样式时,如 background-color,border,visibility,浏览器只会用新的样式将元素重绘一次。
重排:当改变影响到文本内容或结构,或者元素位置时,重排就会发生。
—— 2018/1/19
输入框弹起数字键盘
1 | <input type="tel" novalidate="novalidate" pattern="[0-9]*" id="q2" value="" name="q2" verify="学号" /> |
type="tel"
- 优点是 iOS 和 Android 的键盘表现都差不多
- 缺点是那些字母好多余,虽然我没有强迫症但还是感觉怪怪的啊。
type="number"
- 优点是 Android 下实现的一个真正的数字键盘
- 缺点一:iOS 下不是九宫格键盘,输入不方便
- 缺点二:旧版 Android(包括微信所用的 X5 内核)在输入框后面会有超级鸡肋的小尾巴,好在 Android 4.4.4 以后给去掉了。
不过对于缺点二,我们可以用 webkit 私有的伪元素给 fix 掉:
1 | input[type='number']::-webkit-inner-spin-button, |
pattern
pattern 用于验证表单输入的内容,通常 HTML5 的 type 属性,比如 email、tel、number、data 类、url 等,已经自带了简单的数据格式验证功能了,加上 pattern 后,前端部分的验证更加简单高效了。
novalidate
novalidate 属性规定当提交表单时不对其进行验证
获取对象属性的一些方法
Object.getOwnPropertyNames
获取对象本身属性名,包括不可枚举(enumerable: false;
)属性,不包括Symbol
属性;Object.getOwnPropertySymbols
获取对象本身的Symbol
属性名;Object.keys
获取对象本身属性名,不包括不可枚举属性和Symbol
属性;Reflect.ownKeys
获取对象本身所有属性,等于Object.getOwnPropertyNames
+Object.getOwnPropertySymbols
;key in obj
获取对象本身属性和原型属性,不包括不可枚举属性和Symbol
属性;
1 | const obj = { |
判断是否是数组
1 | var arr = [] |
Object.definedProperty 拦截XMLHttpRequest请求,解决跨域问题
离线和在线资源过期
拦截cookie 同步cookie发生变化