记录看书过程中遇到的一些问题
document.body.addEventListener("mousemove", function() { //#B
var second = document.getElementById("second");
addMessage(second, "Event: mousemove");
},{
once: true,
});
添加第三个参数,这个参数是可选的!once
表示 listener
在添加之后最多只调用一次。如果是true
, listener
会在其被调用之后自动移除。
➹:只执行一次的事件绑定函数 - 个人文章 - SegmentFault 思否
➹:EventTarget.addEventListener() - Web API 接口参考 - MDN
➹:mousemove - Web API 接口参考 - MDN
浏览器厂商和标准组织博弈出来的产物,重要的是明白它们背后的人是谁。WHATWG受到了Opera, Mozilla和Chrome, Safari的支持,而W3C的背后则隐藏着IE这个微软菊苣。私以为在工业发展速度远远超过标准定义的今天,WHATWG或许会更权威一点。关于HTML5标准的定制,最开始是WHATWG在做的,由于到后期大部分浏览器厂商都已经实现了统一标准,W3C想不支持也是不行的啊,这就是传说中的霸王硬上弓?
所以我们就看这个文档就好了,而不是w3c!
➹:whatwg 是个什么组织?和 W3C 的关系是? - SegmentFault 思否
SpiderMonkey 是Mozilla使用C/C++编写的JavaScript 引擎。它被用于包括Firefox在内的多个Mozilla产品中,使用的是MPL 2授权协议.
➹:SpiderMonkey - Mozilla 产品与私有技术 - MDN
比较版本:
结果:
这是2014年2月的比较结果
一句话概括之「V8牛逼!」
➹:javascript引擎 PK: V8 vs Spidermonkey - 程序园
➹:引擎浅谈 SpiderMonkey & Google V8 - 吃代码的兔子窝
➹:★JS 引擎比较 - Mozilla 产品与私有技术 - MDN
浏览器暴露给JavaScript 引擎的主要全局对象是window对象,它代表了包含着一个页面的窗口。window对象是获取所有其他全局对象、全局变量(甚至包含用户定义对象)和浏览器API的访问途径。
在为Web编写代码时,有很多可用的Web API。下面列出了你在开发Web应用程序或站点时可以使用到的所有API以及interfaces(对象类型的接口)。
Web APIs通常与JavaScript一起使用,但并非总是如此。
第一个参数为true,那么第二个参数就有意义了,然后就可以搞事情了!不过一般用于测试代码是否按预期的执行!
function assert(value, text) {
var li = document.createElement("li");
li.className = value ? "pass" : "fail";
li.appendChild(document.createTextNode(text));
var results = document.getElementById("results");
if (!results) {
results = document.createElement("ul");
results.setAttribute('id','results');
document.body.appendChild(results);
}
results.appendChild(li);
}
function pass(text) { assert(true, text); }
function fail(text) { assert(false, text); }
function report(text) { pass(text); }
使用:
assert(store.add(ninja),"Function was safely added.");
assert(!store.add(ninja),"But it was only added once.");
下边这个代码形式上就与assert函数很类似了
document.body.addEventListener("click", function(){ //#C
var second = document.getElementById("second");
addMessage(second, "Event: click");
});
我一直认为只有JavaScript代码执行完毕后才会去解析剩下的HTML元素,结果并非一定是这样的!可能解析了一点JavaScript代码之后,就又会去解析剩下的HTML了!
可这样就不合理了,有些时候肉眼所观测到的不一定是正确的呀!可能v8做了优化,毕竟我那个循环测试是可以用什么JIT编译器优化的
关于事件处理确实只有在callstack清空后,以及DOM树解析完毕后才会触发用户点击过后所执行的回调
还有script2的代码还没有结束,那么当用户点击了在script1时就绑定了事件的按钮,是不会立刻就执行的!
代码分别如下:
当DOM已经就绪并全部构建完成,就会触发这个事件:
window.onload = function(){};
document.body.addEventListener("mousemove", function() {
//为mousemove事件注册处理器
var second = document.getElementById("second");
addMessage(second, "Event: mousemove");
});
document.body.addEventListener("click", function(){
//为click事件注册处理器
var second = document.getElementById("second");
addMessage(second, "Event: click");
});
前者缺点:
对于某个事件只能注册一个事件处理器。也就是说,一不小心就会将上一个事件处理器(所谓的回调函数呀!这个回调函数是浏览器自己去调用的)改写掉
后者优点:
让我们能够注册尽可能多的事件处理器。也就是说,一个事件可以触发多个回调,而且这会根据你所注册的事件处理器的代码顺序决定
因此推荐使用后者!
浏览器接收的HTML代码用作创建DOM的蓝图,它是客户端Web应用结构的内部展示阶段。
即便script标签里边该执行的代码都执行完了,但是我们还是有机会去修改DOM树的节点的,因为我们还有事件处理呀!只要Web页面不关闭,我们就有程序与用户交互!用户做什么动作,我们就会有相应的程序执行给他们看!
一个页面的打开到一个页面的关闭,意味着该页面生命的结束!
方法和对象相关
函数和对象无关
Java:只有“方法”,于是有静态方法和方法这样直接的区别
C++:方法在类里边,即为成员函数,否则,则是函数!
C:只有函数
JavaScript:对象里边的函数叫方法,对象外边的函数叫函数!
总之,它们俩都差不多!只不过方法可以访问对象里边的那些字段,从传参的角度来说,函数是显示传递参数的,而方法通常是隐式传递的!如隐式传个this值!
➹:如何理解 JavaScript 中方法(method)和函数(function)的区别? - 知乎
➹:方法(method)和函数(function)有什么区别? - 文章 - 伯乐在线
➹:oop - What’s the difference between a method and a function? - Stack Overflow
同步回调(不等结果,直接调用):
function useless(ninjaCallback) {
return ninjaCallback();
}
异步调用(等待结果,某个合适的时间点):
document.body.addEventListener("mousemove", function() {
var second = document.getElementById("second");
addMessage(second, "Event: mousemove");
});
基本没啥区别,箭头函数是函数表达式的语法糖!
当用户传的实参数量少于形参数量时,就会出现形参为空值的情况,为了避免这种情况,我们可以使用惰性求值,即兜底求值的做法来解决,不过使用ES6的新特性——默认参数,显然要简洁、方便很多!
//store:仓库、商店之意,用于存储不带重复的函数!
var store = {
nextId: 1, //跟踪下一个要被复制的函数
cache: {}, //使用一个对象作为缓存, 我们可以在其中存储函数
add: function(fn) {
if (!fn.id) {
fn.id = this.nextId++;
this.cache[fn.id] = fn;
return true;
}
} //仅当函数唯一时, 将该函数加入缓存
};
function ninja(){}
assert(store.add(ninja),
"Function was safely added."); //测试上面的代码按预期工作
assert(!store.add(ninja),
"But it was only added once.");
这个代码很有意思: this.cache[fn.id] = fn;
为cache选项对象属性添加的属性是动态,即与这个函数的id是一致的!
之前写的event-hub,用于自定义事件,触发回调:
window.eventHub = {
// 定义一个hash表
events:{
},
// 发布
emit(eventName,data) {
for(let key in this.events) {
if(key === eventName) {
let fnList = this.events[key]
// 推使用map,因为相较于forEach有返回值哈
fnList.map((fn)=>{
fn.call(undefined,data)
})
}
}
},
// 订阅
on(eventName,fn) {
if(this.events[eventName] === undefined) {
this.events[eventName] = []
}
this.events[eventName].push(fn)
}
}
记忆化(memoization)是一种构建函数的处理过程,能够记住上次计算结果
这种函数可以自己曾经接收过的参数,至此,就不需要重新计算了,即直接就可以拿到结果!
举个栗子来说,如(判断一个数是不是素数):
function isPrime(value) {
if (!isPrime.answers) {
isPrime.answers = {};
} //创建缓存,调用函数,然后函数自己为自己添加属性,而不是我们在这个函数外边创建,可见我们这封装很完整
if (isPrime.answers[value] !== undefined) {
return isPrime.answers[value];
} //检查缓存的值
var prime = value !== 1;
//用于计算素数的这个算法很简单,当然,这效率确实不高哈!但
for (var i = 2; i < value; i++) {
if (value % i === 0) {
prime = false;
break;
}
} //如果所传的参数是很大的数的话,那么这个循环的计算量也是挺大的!如果我们重复传相同的参数,
//那么这计算显然也是重复进行的,因此我们的缓存就起作用了,即可以让你无须重复计算,直接拿到结果
return isPrime.answers[value] = prime; //存储计算的值
}
assert(isPrime(5), "5 is prime!" ); //测试该函数是否正常工作
assert(isPrime.answers[5], "The answer was cached!" );
我开始明白看一些优秀的JavaScript源码的意义了!
测试结果:
总之,我们可以按照参数把结果存储起来!函数在,即函数的这个缓存属性也就在了!
与上边那个问题的区别:一个是存粹的放,不要结果的记忆,一个是放,要结果的记忆!
适用场景:
为什么说函数作为一等公民呢?
- 可以作为参数
- 可以回调
- 函数也可以有属性呀!
- ……
对象?一等公民?做梦……
函数声明有只有两种姿势:全局声明,局部声明,必须要名字
函数表达式:名字有没有都行,赋值语句里的一部分(变量和属性)、函数参数、返回值、IIFE等
只要在括号里边,xxx这个标识符有没有都不会作为一个全局变量而存在
一般我们都去掉,毕竟可以少写3个字符呀!
不管是xxx()
这种标识符调用函数,还是 (function(){})()
这种,圆括号 ()
左边的叫做左值,用于求得一个函数值呀!
关于IIFE,我们还有其它姿势 +、-、/、~
,不过这种姿势很奇怪,所以不常使用,总之不管是圆括号,还是奇怪姿势,都是为了向JavaScript引擎发一个信号说你接下来要处理的代码是表达式,而不是语句呀!
还有一点你需要注意的是,+function(){}()
,左值不会在保存起来,当然,我们只需这个函数值被调用了然后执行了即可!
总之,无须关细节,你只需要知道函数被立即执行了!
因为在JavaScript中我们会使用大量的函数,所以添加一些语法糖是有意义的,它使我们能够以更短、更简洁的方式创建函数,从而使我们作为开发人员的生活更愉快
中文本里的书提到:箭头函数它是函数表达式的简化版,但英文版并没有说,而且我们可以直接这样:
总之,关于上来说,可以认为「箭头函数它是函数表达式的简化版」
其语法:
1个参数,一个表达式,最简洁
除此之外,啥都需要有!
它们俩都是术语,而且它们基本一样,几乎可以互换:
请记住是arguments
如果实参数量大于形参的话,不会抛出错误!
需要注意的是,额外的实参,我们也是可以拿到的,这可以通过 arguments
这个伪数组获取!
关于剩余参数(...arg
,拿到的arg
是个数组 ),只有函数的最后一个参数才能是剩余参数。试图把省略号放在不是最后一个形参的任意
形参之前都会报错,错误以SyntaxError: parameter after rest parameter
的形式展现。
关于默认参数:
出现的缘由:
出现后的好处:
不好的:
注意:
总之,实参的值是优先级,没有传过来就用默认值,否则就用传过来的值!
以前我们需要把arguments数组化,如今有了剩余参数之后,这就不需要了! 如我们定义函数的形参时,只有唯一个剩余参数:
对了,还有一个细节你需要注意的是,arguments能够改变我们所传的实参值,有种强迫的调调,即用户传的值不符合我要求,就改了:
注意,这是可逆的过程,后续
a
的值变化了,也会反过来改变arguments[0]
的值!
还有一点:
写了一个小demo,用于测试arguments[0]
与a
的关系
当然,以上这个算法(迭代姿势,简单得一批)很low,于是就有了高斯求和了:
1+2+3..+100
=(1+100)+(2+99)..(50+51)
=101*50
=5050
求和公式
(首项+末项)*项数/2
首项(第一个数)=1
末项(最后一个数)=100
项数(多少个数)=100
所以(1+100)*100/2=5050
结果:
不用再考虑那些边界了
总之,arguments对象是函数参数的别名啦,如果改变了arguments对象的值,同时也会影响对应的函数参数,当然前提是用户调用函数时,是有传实参的,唯有这样,arguments对象才算真正的别名哈!
用一幅图总结之:
似乎打破了我对基本数据类型的认识,因为基本类型数值的复制,是把内存地址空间复制一份的!而在这里则没有了,
arguments[0]
和a
给人的感觉就像是在共享同一份内存地址,但内容却不是一个对象,而是一个基本类型1
呀!或许这存在把1隐式转换为对象?至此,我测试了一下:
这种表现很怪异啊!就像是我们在调用一个函数(直接作为函数调用,而不是方法那样等)时 那样,this不在严格模式下是window,而在严格模式下,是undefined
不管怎样,都应该避免使用把arguments[0]
当作是a
来用,毕竟这会影响代码的可读性!
而且在严格模式下,这是GG的!
在严格模式下 "use strict";
,一开始arguments[0]和a的值一样,但是之后改变了arguments[0]的值,并不会改变a的值!
this的存在似乎就是为JavaScript的面向对象编程而服务的!而不是函数式编程!
this是我们调用函数时所传的隐式参数,而这个参数是JavaScript关于面向对象编程里边的一个重要组成部分,它引用与函数调用相关联的对象。由于这个原因,即,当我们用面向对象姿势调用函数时,这个this并不是独立的存在,而是与函数的调用者有关联,既然有了关联,那么这也就意味着,它在不同调用者里边或者说是环境里边,它的值是不同的,也就是说this的值与函数的调用者有关, 因此我们通常把它被称为函数上下文,表示这个this值是动态的
总之,不同环境下同样的处理会产生不同的结果
形象点来说:
结婚(环境):讲笑话(过程):都高兴(结果)
丧事(环境):讲笑话(过程):你挨揍(结果)
之前我对new有了一定的认识,如JavaScript之父弄了很多糖给我们吃,如this的指向,__proto__
的指向等等
构造函数也是函数,既然是函数,那么我们就可以调用它!
而我们调用它的姿势可不是直接加个圆括号,而是使用了关键字new
new帮我们做了很多事,或者说JavaScript引擎看到new,就对其隔壁的函数调用添加了很多我么开发者无法肉眼见着的东西:
如果我new的时候,不加圆括号呢?
有一点你需要明确的是,当你遇到这样的函数:
function whatsMyContext() {
return this;
}
你开始问这个函数说「函数老兄,我可以把你作为构造函数使用不?」
显然,这是不可能的,因为:
构造函数的目的是创建一个新对象,并进行初始化设置,然后将其作为构造函数的返回值。任何有悖于这两点的情况都不适合作为构造函数。
这个函数,我们返回了值,即便它的结果跟我们用new的结果一致,但是有了return那就是不合适!
那么什么样的函数才可以叫做构造函数呢?
function Ninja() {
this.skulk = function() {
return this;
};
}
这个例子设计得很巧妙,我们通过实例调用skulk所拿到的结果,用于判断this.skulk
的 this
值是多少,毕竟skulk里边的函数上下文,是它调用者对象呀!所以,我们用了「反过来」的做法,就能断定new操作符做了哪些功夫了!
假如构造函数的return值是个对象?
稍微总结一下:
由于这些特性的存在,构造函数的写法一般不同于其他函数的写法!
假如我们把构造函数当作是简单函数调用,或者作为被赋值为对象属性从而作为方法调用?
function Ninja() {
this.skulk = function() {
return this;
};
}
var whatever = Ninja();
这样做其实没有太大意义!
在非严格模式下还好说,而如果在严格模式下的话,构造函数里边的this值就是undefined
了。undefined.skulk
?笑话
你怕是要搞坏你的JavaScript程序哦!
可见,使用严格模式是很有好处的!它可以让我们的JavaScript代码对一些代码行为不显得那么奇怪哈!
为了区分函数、方法和构造函数:
关于函数和方法的命名:通常是描述行为,以动词开头,且第一个字母为小写!
关于构造函数的命名:通常是所构造对象的名词命名,并以大写字母开头!
总之,我们搞出来的是构造函数,那么就作为构造函数去调用,即用new哈!
关于构造函数存在的意义: