扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
之所以突然想写这个文章,主要是之前看到一篇有意思的博文, 《探究点击事件在JavaScript事件循环中的表现》 ,有趣的地方在于JS点击事件加入回调的 并不是点击事件的回调方法 ,而是点击事件本身 (点击位置等描述点击的) 。
创新互联公司主营固安网站建设的网络公司,主营网站建设方案,App定制开发,固安h5微信小程序开发搭建,固安网站营销推广欢迎固安等地区企业咨询
当我们连续点击 button.a 两次的时候,结果却是
明明是点击的 button.a ,为什么会触发 button.b 的事件?
无论通过 onclick 还是 addEventListener 实现事件绑定,我怀疑绑定机制是一样的,因为在实际测试中,我发现 onclick 执行顺序和 addEventListener 是一样的,也就是什么时候绑定,那么就在第几个执行。
addEventListener 和 onclick 不同,不是直接给 dom 绑定属性,并且我在 dom 节点上也没有看到任何相应的对象用于保存事件,可见 addEventListener 是不同的机制,参考 EventEmitter ,试着去实现一个 addEventListener
通过上面分析,总结一下
这个问题挺复杂的,以前看过一次,但没太看懂,这次整理了一下。有兴趣的同学可以看看。
首先打印出0.14和0.14*100的二进制(程序见参考):
0.14 : 0 01111111100 0001111010111000010100011110101110000101000111101100
0.14*100 : 0 10000000010 1100000000000000000000000000000000000000000000000001
14.0 : 0 10000000010 1100000000000000000000000000000000000000000000000000
我们可以看到0.14的小数部分出现循环,就像10/3 = 1.33333…一样,所以0.14是有精度丢失的,
比较0.14*100和14.0,0.14*100多了最后一位1。
Java遵循IEEE 754标准来支持浮点数的操作,结合浮点值0.14,我们来看一下,1) 0.14的二进制格式
0 01111111100 0001111010111000010100011110101110000101000111101100.
根据IEEE 754标准,
注:S:符号(0正1负),E,指数,M小数部分。
对0.14格式化后的结果是:
S(1位) E(11位) M (52位)
0 01111111100 0001111010111000010100011110101110000101000111101100
根据计算公式:
我们可以得到e = 01111111100 – 1023 = 1020 – 1023 = -3
m = 1 (隐藏位) + 0.0001111010111000010100011110101110000101000111101100 = 1 +
Long.valueOf("0001111010111000010100011110101110000101000111101100", 2)/ (Math.pow(2, 52) – 1)
= 1.12000000000000013
n = 1.12000000000000013 *2^-3= 1.12000000000000013/8 约 0.14
接下来,第二个问题,2)为什么0.14 * 100 输出14.000000000000002?
由上可知0.14是不精确的,乘100只会扩大这个不精确度。具体的计算过程如下:
100的浮点二进制:
0 10000000101 1001000000000000000000000000000000000000000000000000
跟据浮点乘法规则指数相加,小数相乘,得到
0.14 * 100 =
2^(6-3) //100的指数是6,0.14的指数是-3
*
(1. 1001000000000000000000000000000000000000000000000000*
1. 0001111010111000010100011110101110000101000111101100) //小数部分
= 2^(6-3) * (1 + 0.1001000000000000000000000000000000000000000000000000 +
0.0001111010111000010100011110101110000101000111101100 +
0. 1001000000000000000000000000000000000000000000000000 * 0. 0001111010111000010100011110101110000101000111101100
//方便计算,分解乘数(同1.1 * 1.1 = 1 + 0.1 * 1 + 1 * 0.1 + 0.1*0.1)
这部分我用计算器计算,貌似精度丢失更严重,但可以得知小数部分依然是循环,而不能精确表达。同时,通过查看JDK中Double.toString(d)方法,我们可以看到SUN实现的浮点输出方法。所以最后打印在页面的是14.000000000000002而不是14.0.
故,浮点是用有限二进位接近表达一个数值,不精确是常态,使用要慎重
参考(强烈推荐)
IEEE 754 浮点数的表示精度探讨
解读IEEE标准754:浮点数表示
浮点乘法计算
打印DOUBLE二进制方法:
public class DoubleTest {
@Test
public void test(){
System.out.println("0.5 : " + this.dumpDouble(0.5));
System.out.println("0.14 : " + this.dumpDouble(0.14));
System.out.println("0.14*100 : " + this.dumpDouble(0.14 * 100));
System.out.println("14.0 : " + this.dumpDouble(14.0));
System.out.println("100 : " + this.dumpDouble(100));
Assert.assertEquals("100.0%", getPercentage(0.9999, 2));
Assert.assertEquals("100%", getPercentage(0.9999, 1));
Assert.assertEquals("99.9%", getPercentage(0.999, 2));
Assert.assertEquals("10.1%", getPercentage(0.101, 2));
Assert.assertEquals("10.2%", getPercentage(0.1015, 2));
Assert.assertEquals("11.0%", getPercentage(0.1095, 2));
Assert.assertEquals("0.11%", getPercentage(0.0011, 3));
final int TOTAL = 100000;
double[] data = new double[TOTAL];
for(int i = 0; i TOTAL; i++ ){
data[i] = Math.random();
}
//通过FORMAT,获得百分比,忽略精度
DecimalFormat f = new DecimalFormat("0.##'.0'%");
long start = System.currentTimeMillis();
for(int i = 0; i TOTAL; i++ ){
f.format(data[i]);
}
long end = System.currentTimeMillis();
System.out.println("0 time: " + (end - start));
//基于字符计算百分比
start = System.currentTimeMillis();
for(int i = 0; i TOTAL; i++ ){
this.getPercentage(data[i], 2);
}
end = System.currentTimeMillis();
System.out.println("1 time: " + (end - start));
}
public String dumpDouble(double d) {
StringBuilder b = new StringBuilder();
String bString = Long.toBinaryString(Double.doubleToLongBits(d));
int fillZeros = 64 - bString.length();
for(int i = 0; i fillZeros ; i++){
if(i == 1){
b.append(' ');
}
b.append('0');
}
for(int i = 0; i bString.length(); i++ ){
if((i + fillZeros) == 1 || (i + fillZeros) == 12){
b.append(' ');
}
b.append(bString.charAt(i));
}
return b.toString();
}
char[] next = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',};
public String getPercentage(double d, int precision){
StringBuilder b = new StringBuilder(Double.toString(d * 100));
int dot = b.indexOf(".");
if(dot 0 dot + precision b.length()){
char c = b.charAt(dot + precision);
//四舍五入
if(c '4'){
int j = dot + precision - 1;
if(j == dot) {
//跳过小数点
j --;
}
char n = next[b.charAt(j) - '0'];
b.setCharAt(j, n);
//向前进1
while(n == '0' j 0){
j --;
if(j == dot) {
//跳过小数点
j --;
}
//+1
n = next[b.charAt(j) - '0'];
b.setCharAt(j, n);
}
if(j == 0){
b.insert(0, 1);
dot++;
if(precision == 1){
b.setCharAt(dot, '%');
return b.substring(0, dot + 1);
}else{
b.setCharAt(dot + precision, '%');
return b.substring(0, dot + precision + 1);
}
}
}
b.setCharAt(dot + precision, '%');
return b.substring(0, dot + precision + 1);
}
b.append("%");
return b.toString();
}
面向对象三大特征:封装、继承、多态
基于封装,引出了js如何创建对象(class、function、原型+闭包创建私有变量等)。这个java和js都差不多
基于继承,引出了js继承的一些知识点,如extends、super、重写重载、abstract(typescript)、interface(typescript)。js主要是基于原型继承,而java主要是基于extends
多态存在的三个必要条件。重写(继承)、重载、向上转型:其中重写,js支持。重载js不支持,可以通过额外处理来支持向上转型,因为js是弱类型语言,不支持数据类型,ts支持(编译阶段)
也就是说对于多态的支持,目前js只支持一种。
总的来说 js的面向对象支持还没有java语言那么丰富严谨,正是因为这些宽松语法,也让js变得更加有趣。但是面向对象oop的思想是一致的。因为oop思想不是固定语言的特性,而是一种思想。随着js不断的更新迭代,相信其语法特性也更加丰富和严谨。
后话:js目前的发展趋势,好像不在跟随传统语言的oop,有点往函数式编程的路上转向
碰壁反弹这个效果真的是挺有趣的,看起来就比较的高大上,而且写碰壁反弹成功后,也就能做些网页小游戏了。虽然这么说,但其实这个效果并不难写。只是坑比较的多,很多工作多年的程序员都可能会陷进去。
废话不多说,我们写起来:
1.CSS文件:
style
body{
border-style: solid;
border-color: sliver;
border-width: 5px;
margin-left: 100px;
width:1000px;
height:800px;
}
#screen{
width:800px;
height:600px;
background-color:#272822;
position:relative;
left:100px;
top:100px;
}
#egg{
width:50px;
height:50px;
background-color:red;
position:relative;
}
/style
2.body内容:
body
div id="screen"
div id="egg"
/div
/div
/body
3.JavaScript脚本:
var eggX=0;
var eggY=0;
var directX=1;
var directY=1;
function test(){
eggX+=directX;
eggY+=directY;
egg.style.left=eggX+"px";
egg.style.top=eggY+"px";
if(eggX+egg.offsetWidth=document.getElementById("screen").clientWidth||eggX=0) {
directX=-directX;
}
if(eggY+egg.offsetHeight=document.getElementById("screen").clientHeight||eggY=0){
directY=-directY;
}
}
setInterval("test()",1);
说明:拷贝代码,我们就能看到一个碰壁反弹的方框了。当然如果想要换成图片,直接将div里的内容换掉就OK了。不过这样似乎太美水准了点,在这附上完整代码(有障碍物的):
!DOCTYPE html
html
head
meta http-equiv="content-type" content="text/html; charset=UTF-8"
title碰壁反弹/title
style
body{
border-style: solid;
border-color: sliver;
border-width: 5px;
margin-left: 100px;
width:1000px;
height:800px;
}
#screen{
width:800px;
height:600px;
background-color:#272822;
position:relative;
left:100px;
top:100px;
}
#egg{
width:50px;
height:50px;
background-color:red;
position:relative;
}
#cock{
position:relative;
left:200px;
top:200px;
width:200px;
height:30px;
background-color: blue;
cursor: pointer;
}
/style
/head
body
div id="screen"
div id="egg"
/div
div id="cock" onmousedown="startDrag(this)" onmousemove="drag(this)" onmouseup="stopDrag(this)"
/div
/div
/body
script type="text/javascript"
var eggX=0;
var eggY=0;
var directX=1;
var directY=1;
function test(){
eggX+=directX;
eggY+=directY;
egg.style.left=eggX+"px";
egg.style.top=eggY+"px";
if(eggX+egg.offsetWidth=document.getElementById("screen").clientWidth||eggX=0) {
directX=-directX;
}
if(eggY+egg.offsetHeight=document.getElementById("screen").clientHeight||eggY=0){
directY=-directY;
}
if(eggY+egg.offsetHeight=document.getElementById("cock").offsetTop(eggX+egg.offsetWidth=200eggX+egg.offsetWidth=400)){
directY=-directY;
}
if((eggY-30)=document.getElementById("cock").offsetTop(eggX+egg.offsetWidth=200eggX+egg.offsetWidth=400)){
directY=-directY;
}
}
setInterval("test()",1);
/script
/html
你好!
JavaScript的诞生首先就是网页脚本
之后的html api让JavaScript有了操作DOM和渲染DOM的能力,于是可以生成整个网页
苹果又加了料,在webkit component里加入了canvas /,于是js可以绘图
后来微软提出的的ajax和新的H5脚本诞生了SPA的概念,于是有了网页应用
我能画画,我能交互,我要成为高达!于是基于JavaScript的网页游戏如雨后春笋
Ryan Dahl这个人想搞点大事情,“要有服务器”,于是有了 NodeJS
希望我的回答能帮助你!
望采纳谢谢????
祝生活愉快!
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流