对this对象的了解
this 是执行上下文中的一个属性,它指向最初一次调用这个办法的对象。在理论开发中,this 的指向能够通过四种调用模式来判断。
- 第一种是函数调用模式,当一个函数不是一个对象的属性时,间接作为函数来调用时,this 指向全局对象。
- 第二种是办法调用模式,如果一个函数作为一个对象的办法来调用时,this 指向这个对象。
- 第三种是结构器调用模式,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。
- 第四种是 apply 、 call 和 bind 调用模式,这三个办法都能够显示的指定调用函数的 this 指向。其中 apply 办法接管两个参数:一个是 this 绑定的对象,一个是参数数组。call 办法接管的参数,第一个是 this 绑定的对象,前面的其余参数是传入函数执行的参数。也就是说,在应用 call() 办法时,传递给函数的参数必须一一列举进去。bind 办法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了应用 new 时会被扭转,其余状况下都不会扭转。
这四种形式,应用结构器调用模式的优先级最高,而后是 apply、call 和 bind 调用模式,而后是办法调用模式,而后是函数调用模式。
为什么须要浏览器缓存?
对于浏览器的缓存,次要针对的是前端的动态资源,最好的成果就是,在发动申请之后,拉取相应的动态资源,并保留在本地。如果服务器的动态资源没有更新,那么在下次申请的时候,就间接从本地读取即可,如果服务器的动态资源曾经更新,那么咱们再次申请的时候,就到服务器拉取新的资源,并保留在本地。这样就大大的缩小了申请的次数,进步了网站的性能。这就要用到浏览器的缓存策略了。
所谓的浏览器缓存指的是浏览器将用户申请过的动态资源,存储到电脑本地磁盘中,当浏览器再次拜访时,就能够间接从本地加载,不须要再去服务端申请了。
应用浏览器缓存,有以下长处:
- 缩小了服务器的累赘,进步了网站的性能
- 放慢了客户端网页的加载速度
- 缩小了多余网络数据传输
代码输入后果
var x = 3;var y = 4;var obj = { x: 1, y: 6, getX: function() { var x = 5; return function() { return this.x; }(); }, getY: function() { var y = 7; return this.y; }}console.log(obj.getX()) // 3console.log(obj.getY()) // 6
输入后果:3 6
解析:
- 咱们晓得,匿名函数的this是指向全局对象的,所以this指向window,会打印出3;
- getY是由obj调用的,所以其this指向的是obj对象,会打印出6。
什么是作用域链?
首先要理解作用域链,当拜访一个变量时,编译器在执行这段代码时,会首先从以后的作用域中查找是否有这个标识符,如果没有找到,就会去父作用域查找,如果父作用域还没找到持续向上查找,直到全局作用域为止,,而作用域链,就是有以后作用域与下层作用域的一系列变量对象组成,它保障了以后执行的作用域对合乎拜访权限的变量和函数的有序拜访。
call/apply/bind 的实现
call
形容:应用 一个指定的 this
值(默认为 window
) 和 一个或多个参数 来调用一个函数。
语法:function.call(thisArg, arg1, arg2, ...)
核心思想:
- 调用call 的可能不是函数
- this 可能传入 null
- 传入不固定个数的参数
- 给对象绑定函数并调用
- 删除绑定的函数
- 函数可能有返回值
实现:
Function.prototype.call1 = function(context, ...args) { if(typeof this !== "function") { throw new TypeError("this is not a function"); } context = context || window; // 如果传入的是null, 则指向window let fn = Symbol('fn'); // 发明惟一的key值,作为结构的context外部办法名 context[fn] = this; // 为 context 绑定原函数(this) let res = context[fn](...args); // 调用原函数并传参, 保留返回值用于call返回 delete context[fn]; // 删除对象中的函数, 不能批改对象 return res;}
apply
形容:与 call
相似,惟一的区别就是 call
是传入不固定个数的参数,而 apply
是传入一个参数数组或类数组。
实现:
Function.prototype.apply1 = function(context, arr) { if(typeof this !== "function") { throw new TypeError("this is not a function"); } context = context || window; // 如果传入的是null, 则指向window let fn = Symbol('fn'); // 发明惟一的key值,作为结构的context外部办法名 context[fn] = this; // 为 context 绑定原函数(this) let res; // 判断是否传入的数组是否为空 if(!arr) { res = context[fn](); } else { res = context[fn](...arr); // 调用原函数并传参, 保留返回值用于call返回 } delete context[fn]; // 删除对象中的函数, 不能批改对象 return res;}
bind
形容:bind
办法会创立一个新的函数,在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时应用。
核心思想:
- 调用bind的可能不是函数
- bind() 除了 this 外,还可传入多个参数
- bind() 创立的新函数可能传入多个参数
- 新函数可能被当做结构函数调用
- 函数可能有返回值
实现:
Function.prototype.bind1 = function(context, ...args) { if (typeof that !== "function") { throw new TypeError("this is not function"); } let that = this; // 保留原函数(this) return function F(...innerArgs) { // 判断是否是 new 构造函数 // 因为这里是调用的 call 办法,因而不须要判断 context 是否为空 return that.call(this instanceof F ? this : context, ...args, ...innerArgs); }}
new 实现
形容:new
运算符用来创立用户自定义的对象类型的实例或者具备构造函数的内置对象的实例。
核心思想:
- new 会产生一个新对象
- 新对象须要可能拜访到构造函数的属性,所以须要从新指定它的原型
- 构造函数可能会显示返回对象与根本类型的状况(以及null)
步骤:应用new
命令时,它前面的函数顺次执行上面的步骤:
- 创立一个空对象,作为将要返回的对象实例。
- 将这个空对象的隐式原型(
__proto__
),指向构造函数的prototype
属性。 - 让函数外部的
this
关键字指向这个对象。开始执行构造函数外部的代码(为这个新对象增加属性)。 - 判断函数的返回值类型,如果是值类型,返回创立的对象。如果是援用类型,就返回这个援用类型的对象。
实现:
// 写法一:function myNew() { // 将 arguments 对象转为数组 let args = [].slice.call(arguments); // 取出构造函数 let constructor = args.shift(); // 创立一个空对象,继承构造函数的 prototype 属性 let obj = {}; obj.__proto__ = constructor.prototype; // 执行构造函数并将 this 绑定到新创建的对象上 let res = constructor.call(obj, ...args); // let res = constructor.apply(obj, args); // 判断构造函数执行返回的后果。如果返回后果是援用类型,就间接返回,否则返回 obj 对象 return (typeof res === "object" && res !== null) ? res : obj;}// 写法二:constructor:构造函数, ...args:结构函数参数function myNew(constructor, ...args) { // 生成一个空对象,继承构造函数的 prototype 属性 let obj = Object.create(constructor.prototype); // 执行构造函数并将 this 绑定到新创建的对象上 let res = constructor.call(obj, ...args); // let res = constructor.apply(obj, args); // 判断构造函数执行返回的后果。如果返回后果是援用类型,就间接返回,否则返回 obj 对象 return (typeof res === "object" && res !== null) ? res : obj;}
对事件循环的了解
因为 js 是单线程运行的,在代码执行时,通过将不同函数的执行上下文压入执行栈中来保障代码的有序执行。在执行同步代码时,如果遇到异步事件,js 引擎并不会始终期待其返回后果,而是会将这个事件挂起,继续执行执行栈中的其余工作。当异步事件执行结束后,再将异步事件对应的回调退出到一个工作队列中期待执行。工作队列能够分为宏工作队列和微工作队列,当以后执行栈中的事件执行结束后,js 引擎首先会判断微工作队列中是否有工作能够执行,如果有就将微工作队首的事件压入栈中执行。当微工作队列中的工作都执行实现后再去执行宏工作队列中的工作。
Event Loop 执行程序如下所示:
- 首先执行同步代码,这属于宏工作
- 当执行完所有同步代码后,执行栈为空,查问是否有异步代码须要执行
- 执行所有微工作
- 当执行完所有微工作后,如有必要会渲染页面
- 而后开始下一轮 Event Loop,执行宏工作中的异步代码
参考 前端进阶面试题具体解答
JS 隐式转换,显示转换
个别非根底类型进行转换时会先调用 valueOf,如果 valueOf 无奈返回根本类型值,就会调用 toString
字符串和数字
- "+" 操作符,如果有一个为字符串,那么都转化到字符串而后执行字符串拼接
- "-" 操作符,转换为数字,相减 (-a, a * 1 a/1) 都能进行隐式强制类型转换
[] + {} 和 {} + []
布尔值到数字
- 1 + true = 2
- 1 + false = 1
转换为布尔值
- for 中第二个
- while
- if
- 三元表达式
- || (逻辑或) && (逻辑与)右边的操作数
符号
- 不能被转换为数字
- 能被转换为布尔值(都是 true)
- 能够被转换成字符串 "Symbol(cool)"
宽松相等和严格相等
宽松相等容许进行强制类型转换,而严格相等不容许
字符串与数字
转换为数字而后比拟
其余类型与布尔类型
- 先把布尔类型转换为数字,而后持续进行比拟
对象与非对象
- 执行对象的 ToPrimitive(对象)而后持续进行比拟
假值列表
- undefined
- null
- false
- +0, -0, NaN
- ""
回流与重绘的概念及触发条件
(1)回流
当渲染树中局部或者全副元素的尺寸、构造或者属性发生变化时,浏览器会从新渲染局部或者全副文档的过程就称为回流。
上面这些操作会导致回流:
- 页面的首次渲染
- 浏览器的窗口大小发生变化
- 元素的内容发生变化
- 元素的尺寸或者地位发生变化
- 元素的字体大小发生变化
- 激活CSS伪类
- 查问某些属性或者调用某些办法
- 增加或者删除可见的DOM元素
在触发回流(重排)的时候,因为浏览器渲染页面是基于流式布局的,所以当触发回流时,会导致四周的DOM元素重新排列,它的影响范畴有两种:
- 全局范畴:从根节点开始,对整个渲染树进行从新布局
- 部分范畴:对渲染树的某局部或者一个渲染对象进行从新布局
(2)重绘
当页面中某些元素的款式发生变化,然而不会影响其在文档流中的地位时,浏览器就会对元素进行从新绘制,这个过程就是重绘。
上面这些操作会导致回流:
- color、background 相干属性:background-color、background-image 等
- outline 相干属性:outline-color、outline-width 、text-decoration
- border-radius、visibility、box-shadow
留神: 当触发回流时,肯定会触发重绘,然而重绘不肯定会引发回流。
数组去重
ES5 实现:
function unique(arr) { var res = arr.filter(function(item, index, array) { return array.indexOf(item) === index }) return res}
ES6 实现:
var unique = arr => [...new Set(arr)]
基于 Localstorage 设计一个 1M 的缓存零碎,须要实现缓存淘汰机制
设计思路如下:
- 存储的每个对象须要增加两个属性:别离是过期工夫和存储工夫。
- 利用一个属性保留零碎中目前所占空间大小,每次存储都减少该属性。当该属性值大于 1M 时,须要依照工夫排序零碎中的数据,删除一定量的数据保障可能存储下目前须要存储的数据。
- 每次取数据时,须要判断该缓存数据是否过期,如果过期就删除。
以下是代码实现,实现了思路,然而可能会存在 Bug,然而这种设计题个别是给出设计思路和局部代码,不会须要写出一个无问题的代码
class Store { constructor() { let store = localStorage.getItem('cache') if (!store) { store = { maxSize: 1024 * 1024, size: 0 } this.store = store } else { this.store = JSON.parse(store) } } set(key, value, expire) { this.store[key] = { date: Date.now(), expire, value } let size = this.sizeOf(JSON.stringify(this.store[key])) if (this.store.maxSize < size + this.store.size) { console.log('超了-----------'); var keys = Object.keys(this.store); // 工夫排序 keys = keys.sort((a, b) => { let item1 = this.store[a], item2 = this.store[b]; return item2.date - item1.date; }); while (size + this.store.size > this.store.maxSize) { let index = keys[keys.length - 1] this.store.size -= this.sizeOf(JSON.stringify(this.store[index])) delete this.store[index] } } this.store.size += size localStorage.setItem('cache', JSON.stringify(this.store)) } get(key) { let d = this.store[key] if (!d) { console.log('找不到该属性'); return } if (d.expire > Date.now) { console.log('过期删除'); delete this.store[key] localStorage.setItem('cache', JSON.stringify(this.store)) } else { return d.value } } sizeOf(str, charset) { var total = 0, charCode, i, len; charset = charset ? charset.toLowerCase() : ''; if (charset === 'utf-16' || charset === 'utf16') { for (i = 0, len = str.length; i < len; i++) { charCode = str.charCodeAt(i); if (charCode <= 0xffff) { total += 2; } else { total += 4; } } } else { for (i = 0, len = str.length; i < len; i++) { charCode = str.charCodeAt(i); if (charCode <= 0x007f) { total += 1; } else if (charCode <= 0x07ff) { total += 2; } else if (charCode <= 0xffff) { total += 3; } else { total += 4; } } } return total; }}
什么是原型什么是原型链?
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body></body><script> function Person () { } var person = new Person(); person.name = 'Kevin'; console.log(person.name) // Kevin // prototype function Person () { } Person.prototype.name = 'Kevin'; var person1 = new Person(); var person2 = new Person(); console.log(person1.name)// Kevin console.log(person2.name)// Kevin // __proto__ function Person () { } var person = new Person(); console.log(person.__proto__ === Person.prototype) // true //constructor function Person() { } console.log(Person === Person.prototype.constructor) // true //综上所述 function Person () { } var person = new Person() console.log(person.__proto__ == Person.prototype) // true console.log(Person.prototype.constructor == Person) // true //顺便学习一下ES5得办法,能够取得对象得原型 console.log(Object.getPrototypeOf(person) === Person.prototype) // true //实例与原型 function Person () { } Person.prototype.name = 'Kevin'; var person = new Person(); person.name = 'Daisy'; console.log(person.name) // Daisy delete person.name; console.log(person.name) // Kevin //原型得原型 var obj = new Object(); obj.name = 'Kevin', console.log(obj.name) //Kevin //原型链 console.log(Object.prototype.__proto__ === null) //true // null 示意"没用对象" 即该处不应该有值 // 补充 function Person() { } var person = new Person() console.log(person.constructor === Person) // true //当获取person.constructor时,其实person中并没有constructor属性,当不能读取到constructor属性时,会从person的原型 //也就是Person.prototype中读取时,正好原型中有该属性,所以 person.constructor === Person.prototype.constructor //__proto__ //其次是__proto__,绝大部分浏览器都反对这个非标准的办法拜访原型,然而它并不存在于Person.prototype中,实际上,它 // 是来自与Object.prototype,与其说是一个属性,不如说是一个getter/setter,当应用obj.__proto__时,能够了解成返回了 // Object.getPrototypeOf(obj) 总结: 1、当一个对象查找属性和办法时会从本身查找,如果查找不到则会通过__proto__指向被实例化的构造函数的prototype 2、隐式原型也是一个对象,是指向咱们构造函数的原型 3、除了最顶层的Object对象没有__proto_,其余所有的对象都有__proto__,这是隐式原型 4、隐式原型__proto__的作用是让对象通过它来始终往上查找属性或办法,直到找到最顶层的Object的__proto__属性,它的值是null,这个查找的过程就是原型链</script></html>
说一下for...in 和 for...of的区别?
for...of遍历获取的是对象的键值, for...in获取的是对象的键名;for...in会遍历对象的整个原型链, 性能十分差不举荐应用,而for...of只遍历以后对象不会遍历原型链;对于数组的遍历,for...in会返回数组中所有可枚举的属性(包含原型链上可枚举的属性),for...of只返回数组的下标对应的属性值;总结:for...in循环次要是为了遍历对象而生,不实用遍历数组; for....of循环能够用来遍历数组、类数组对象、字符串、Set、Map以及Generator对象
Set,Map解构
ES6 提供了新的数据结构 Set。它相似于数组,然而成员的值都是惟一的,没有反复的值。 Set 自身是一个构造函数,用来生成 Set 数据结构。ES6 提供了 Map 数据结构。它相似于对象,也是键值对的汇合,然而“键”的范畴不限于字符串,各种类型的值(包含对象)都能够当作键。
浏览器渲染优化
(1)针对JavaScript: JavaScript既会阻塞HTML的解析,也会阻塞CSS的解析。因而咱们能够对JavaScript的加载形式进行扭转,来进行优化:
(1)尽量将JavaScript文件放在body的最初
(2) body两头尽量不要写<script>
标签
(3)<script>
标签的引入资源形式有三种,有一种就是咱们罕用的间接引入,还有两种就是应用 async 属性和 defer 属性来异步引入,两者都是去异步加载内部的JS文件,不会阻塞DOM的解析(尽量应用异步加载)。三者的区别如下:
- script 立刻进行页面渲染去加载资源文件,当资源加载结束后立刻执行js代码,js代码执行结束后持续渲染页面;
- async 是在下载实现之后,立刻异步加载,加载好后立刻执行,多个带async属性的标签,不能保障加载的程序;
- defer 是在下载实现之后,立刻异步加载。加载好后,如果 DOM 树还没构建好,则先等 DOM 树解析好再执行;如果DOM树曾经筹备好,则立刻执行。多个带defer属性的标签,依照程序执行。
(2)针对CSS:应用CSS有三种形式:应用link、@import、内联款式,其中link和@import都是导入内部款式。它们之间的区别:
- link:浏览器会派发一个新等线程(HTTP线程)去加载资源文件,与此同时GUI渲染线程会持续向下渲染代码
- @import:GUI渲染线程会临时进行渲染,去服务器加载资源文件,资源文件没有返回之前不会持续渲染(妨碍浏览器渲染)
- style:GUI间接渲染
内部款式如果长时间没有加载结束,浏览器为了用户体验,会应用浏览器会默认款式,确保首次渲染的速度。所以CSS个别写在headr中,让浏览器尽快发送申请去获取css款式。
所以,在开发过程中,导入内部款式应用link,而不必@import。如果css少,尽可能采纳内嵌款式,间接写在style标签中。
(3)针对DOM树、CSSOM树: 能够通过以下几种形式来缩小渲染的工夫:
- HTML文件的代码层级尽量不要太深
- 应用语义化的标签,来防止不规范语义化的非凡解决
- 缩小CSSD代码的层级,因为选择器是从左向右进行解析的
(4)缩小回流与重绘:
- 操作DOM时,尽量在低层级的DOM节点进行操作
- 不要应用
table
布局, 一个小的改变可能会使整个table
进行从新布局 - 应用CSS的表达式
- 不要频繁操作元素的款式,对于动态页面,能够批改类名,而不是款式。
- 应用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其余元素
- 防止频繁操作DOM,能够创立一个文档片段
documentFragment
,在它下面利用所有DOM操作,最初再把它增加到文档中 - 将元素先设置
display: none
,操作完结后再把它显示进去。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。 - 将DOM的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于浏览器的渲染队列机制。
浏览器针对页面的回流与重绘,进行了本身的优化——渲染队列
浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了肯定的数量或者到了肯定的工夫距离,浏览器就会对队列进行批处理。这样就会让屡次的回流、重绘变成一次回流重绘。
将多个读操作(或者写操作)放在一起,就会等所有的读操作进入队列之后执行,这样,本来应该是触发屡次回流,变成了只触发一次回流。
代码输入后果
Promise.reject('err!!!') .then((res) => { console.log('success', res) }, (err) => { console.log('error', err) }).catch(err => { console.log('catch', err) })
输入后果如下:
error err!!!
咱们晓得,.then
函数中的两个参数:
- 第一个参数是用来解决Promise胜利的函数
- 第二个则是解决失败的函数
也就是说Promise.resolve('1')
的值会进入胜利的函数,Promise.reject('2')
的值会进入失败的函数。
在这道题中,谬误间接被then
的第二个参数捕捉了,所以就不会被catch
捕捉了,输入后果为:error err!!!'
然而,如果是像上面这样:
Promise.resolve() .then(function success (res) { throw new Error('error!!!') }, function fail1 (err) { console.log('fail1', err) }).catch(function fail2 (err) { console.log('fail2', err) })
在then
的第一参数中抛出了谬误,那么他就不会被第二个参数不活了,而是被前面的catch
捕捉到。
如何⽤webpack来优化前端性能?
⽤webpack优化前端性能是指优化webpack的输入后果,让打包的最终后果在浏览器运⾏疾速⾼效。
- 压缩代码:删除多余的代码、正文、简化代码的写法等等⽅式。能够利⽤webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩JS⽂件, 利⽤ cssnano (css-loader?minimize)来压缩css
- 利⽤CDN减速: 在构建过程中,将引⽤的动态资源门路批改为CDN上对应的门路。能够利⽤webpack对于 output 参数和各loader的 publicPath 参数来批改资源门路
- Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。能够通过在启动webpack时追加参数 --optimize-minimize 来实现
- Code Splitting: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时能够充沛利⽤浏览器缓存
- 提取公共第三⽅库: SplitChunksPlugin插件来进⾏公共模块抽取,利⽤浏览器缓存能够⻓期缓存这些⽆需频繁变动的公共代码
实现有并行限度的 Promise 调度器
题目形容:JS 实现一个带并发限度的异步调度器 Scheduler,保障同时运行的工作最多有两个
addTask(1000,"1"); addTask(500,"2"); addTask(300,"3"); addTask(400,"4"); 的输入程序是:2 3 1 4 整个的残缺执行流程:一开始1、2两个工作开始执行500ms时,2工作执行结束,输入2,工作3开始执行800ms时,3工作执行结束,输入3,工作4开始执行1000ms时,1工作执行结束,输入1,此时只剩下4工作在执行1200ms时,4工作执行结束,输入4
实现代码如下:
class Scheduler { constructor(limit) { this.queue = []; this.maxCount = limit; this.runCounts = 0; } add(time, order) { const promiseCreator = () => { return new Promise((resolve, reject) => { setTimeout(() => { console.log(order); resolve(); }, time); }); }; this.queue.push(promiseCreator); } taskStart() { for (let i = 0; i < this.maxCount; i++) { this.request(); } } request() { if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) { return; } this.runCounts++; this.queue .shift()() .then(() => { this.runCounts--; this.request(); }); }}const scheduler = new Scheduler(2);const addTask = (time, order) => { scheduler.add(time, order);};addTask(1000, "1");addTask(500, "2");addTask(300, "3");addTask(400, "4");scheduler.taskStart();
数组去重
实现代码如下:
function uniqueArr(arr) { return [...new Set(arr)];}
为什么 0.1 + 0.2 != 0.3,请详述理由
因为 JS 采纳 IEEE 754 双精度版本(64位),并且只有采纳 IEEE 754 的语言都有该问题。
咱们都晓得计算机示意十进制是采纳二进制示意的,所以 0.1
在二进制示意为
// (0011) 示意循环0.1 = 2^-4 * 1.10011(0011)
那么如何失去这个二进制的呢,咱们能够来演算下
小数算二进制和整数不同。乘法计算时,只计算小数位,整数位用作每一位的二进制,并且失去的第一位为最高位。所以咱们得出 0.1 = 2^-4 * 1.10011(0011)
,那么 0.2
的演算也根本如上所示,只须要去掉第一步乘法,所以得出 0.2 = 2^-3 * 1.10011(0011)
。
回来持续说 IEEE 754 双精度。六十四位中符号位占一位,整数位占十一位,其余五十二位都为小数位。因为 0.1
和 0.2
都是有限循环的二进制了,所以在小数位开端处须要判断是否进位(就和十进制的四舍五入一样)。
所以 2^-4 * 1.10011...001
进位后就变成了 2^-4 * 1.10011(0011 * 12次)010
。那么把这两个二进制加起来会得出 2^-2 * 1.0011(0011 * 11次)0100
, 这个值算成十进制就是 0.30000000000000004
上面说一下原生解决办法,如下代码所示
parseFloat((0.1 + 0.2).toFixed(10))
如何阻止事件冒泡
- 一般浏览器应用:event.stopPropagation()
- IE浏览器应用:event.cancelBubble = true;