今天本来在看js的commonJs,amd,cmd,umd这些模块内容
看着,看着,突然看到了gitbook的内容
发现很多人,都用这个,写一个类似,api或者一些列的书籍用
所以下午花时间,开始弄这个
开始也是啥不懂,慢慢总算摸清门路了
其实还是不会用
本来去掉了让人讨厌的,最下面的那个published with gitbook那个连接
结果和github同步了一次代码,全部丢失了
后来实在,是不想弄了,就留着吧,懒得弄啦
最后的解决方案就是
用gitbook的那个编辑器,直接写文章将来
或者像这个博客一样,全部用markdown的语法,自己写
显得的高大上
去我的github上面,可以找一下,gitbook的写作模板
顺便把首页的测试页链接,改为自己的书籍,将来补充一下,争取写成一个书单,或者笔记之类的东西吧
花了三天的时间,终于把大叔的博客,JavaScript系列看了一遍
收获颇丰啊,不愧是经典,一下子很多零散的东西就串联了起来
先简单说一下背景吧
大概是13年左右的时候,就有人推荐大叔的博客,那时第一个原因是水平不够,第二个没时间,总之就是没看过,但是听说过了
后来,陆续又自己完完整整的学了一遍JavaScript,就是大叔最后一篇文章里面推荐的,初级书籍,那边三圣经之一,看完了总觉得学会了,其实
看完就忘记了,不过有个大概印象了
再后来,毕业工作了,去做java后端了,因为有时候需要写简单的前端页面,就开始到了真正的实际应用,开始写的过程,才慢慢对一些概念,有了自己的认识
但是,还是停留在最初级的阶段,写个function函数,那种,变量都是全局的,简单的子自行,this分不清的水平
慢慢的,有个机会让我去定制前端组件,那个时候,自己开始模仿的写一些组件,但是都是初级的,不过正是因为这个原因,才有了一次写js的锻炼,有了这种训练
以前很多的概念开始,慢慢用上了
大概自认为,初级JavaScript之上,又达不到中级的水平吧,
大叔的系列,看完一遍,对我来说的收获
说了这么多,其实呢,这个东西,不实践还是没有用的,如果没有当初自己一个人开始写js的那段实践,我估计也不会有今天
看博客那么大的收获
不敢说,都看懂了大叔所写,但是我心中的疑惑对js的,那些模糊的概念突然清晰了起来
书不是读一遍,就完事的,等以后有时间,再返回头看看,可能有更多的收获
上图
挑选大叔里面,个人自己喜欢的设计模式实现
在传统开发工程师眼里,单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。
|
|
这个设计模式,简单明了,通过this进行缓存,剩下了去判断不存在的麻烦
构造函数用于创建特定类型的对象——不仅声明了使用的对象,构造函数还可以接受参数以便第一次创建对象的时候设置对象的成员值。你可以自定义自己的构造函数,然后在里面声明自定义类型对象的属性或方法。
建造者模式可以将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。也就是说如果我们用了建造者模式,那么用户就需要指定需要建造的类型就可以得到它们,而具体建造的过程和细节就不需要知道了。
建造者模式主要用于“分步骤构建一个复杂的对象”,在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化,其优点是:建造者模式的“加工工艺”是暴露的,这样使得建造者模式更加灵活,并且建造者模式解耦了组装过程和创建具体部件,使得我们不用去关心每个部件是如何组装的。
也就是说,上面的函数中,getBeerById函数是稳定的,写了一个构建过程,而具体的构建细节由另外一个回调函数决定
其实,我觉得,这个部分,不属于java里面的建造者模式,更是一种模板模式,可能个人理解不同吧,反正大叔的意思,就是这种样子
什么时候使用工厂模式
以下几种情景下工厂模式特别有用:
对象的构建十分复杂
需要依赖具体环境创建不同实例
处理大量具有相同属性的小对象
|
|
一句话,就是根据不同的类型,调用不同的子类,进行实例化一个对象
装饰者用于通过重载方法的形式添加新功能,该模式可以在被装饰者前面或者后面加上自己的行为以达到特定的目的。
|
|
这段代码,主要是在于,
tree.getDecorator 这段函数设计精妙
他让tree接受一个新的函数,而每次都用这个新函数去替代之前的函数,并且同时让原型指向前一个,实现了一个
继承关系,比如A-》B-》C,最开始tree是A,然后,放入一个,就变成B,之后为C
外观模式不仅简化类中的接口,而且对接口与调用者也进行了解耦。外观模式经常被认为开发者必备,它可以将一些复杂操作封装起来,并创建一个简单的接口用于调用。
外观模式经常被用于JavaScript类库里,通过它封装一些接口用于兼容多浏览器,外观模式可以让我们间接调用子系统,从而避免因直接访问子系统而产生不必要的错误。
外观模式的优势是易于使用,而且本身也比较轻量级。但也有缺点 外观模式被开发者连续使用时会产生一定的性能问题,因为在每次调用时都要检测功能的可用性。
|
|
代理,顾名思义就是帮助别人做事
观察者模式又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。
|
|
策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户。
|
|
命令模式(Command)的定义是:用于将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及执行可撤销的操作。也就是说改模式旨在将函数的调用、请求和操作封装成一个单一的对象,然后对这个对象进行一系列的处理。此外,可以通过调用实现具体函数的对象来解耦命令对象与接收对象。
|
|
迭代器模式(Iterator):提供一种方法顺序一个聚合对象中各个元素,而又不暴露该对象内部表示。
迭代器的几个特点是:
访问一个聚合对象的内容而无需暴露它的内部表示。
为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。
遍历的同时更改迭代器所在的集合结构可能会导致问题(比如C#的foreach里不允许修改item)。
|
|
软件开发中,中介者是一个行为设计模式,通过提供一个统一的接口让系统的不同部分进行通信。一般,如果系统有很多子模块需要直接沟通,都要创建一个中央控制点让其各模块通过该中央控制点进行交互。中介者模式可以让这些子模块不需要直接沟通,而达到进行解耦的目的。
打个比方,平时常见的机场交通控制系统,塔台就是中介者,它控制着飞机(子模块)的起飞和降落,因为所有的沟通都是从飞机向塔台汇报来完成的,而不是飞机之前相互沟通。中央控制系统就是该系统的关键,也就是软件设计中扮演的中介者角色。
|
|
另外,由于中介者模式把交互复杂性变成了中介者本身的复杂性,所以说中介者对象会比其它任何对象都复杂。
Flyweight中有两个重要概念–内部状态intrinsic和外部状态extrinsic之分,内部状态就是在对象里通过内部方法管理,而外部信息可以在通过外部删除或者保存。
说白点,就是先捏一个的原始模型,然后随着不同场合和环境,再产生各具特征的具体模型,很显然,在这里需要产生不同的新对象,所以Flyweight模式中常出现Factory模式,
|
|
职责链模式(Chain of responsibility)是使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。
也就是说,请求以后,从第一个对象开始,链中收到请求的对象要么亲自处理它,要么转发给链中的下一个候选者。提交请求的对象并不明确知道哪一个对象将会处理它——也就是该请求有一个隐式的接受者(implicit receiver)。根据运行时刻,任一候选者都可以响应相应的请求,候选者的数目是任意的,你可以在运行时刻决定哪些候选者参与到链中。
|
|
适配器模式(Adapter)是将一个类(对象)的接口(方法或属性)转化成客户希望的另外一个接口(方法或属性),适配器模式使得原本由于接口不兼容而不能一起工作的那些类(对象)可以一些工作。速成包装器(wrapper)。
|
|
那合适使用适配器模式好呢?如果有以下情况出现时,建议使用:
使用一个已经存在的对象,但其方法或属性接口不符合你的要求;
你想创建一个可复用的对象,该对象可以与其它不相关的对象或不可见对象(即接口方法或属性不兼容的对象)协同工作;
想使用已经存在的对象,但是不能对每一个都进行原型继承以匹配它的接口。对象适配器可以适配它的父对象接口方法或属性。
另外,适配器模式和其它几个模式可能容易让人迷惑,这里说一下大概的区别:
适配器和桥接模式虽然类似,但桥接的出发点不同,桥接的目的是将接口部分和实现部分分离,从而对他们可以更为容易也相对独立的加以改变。而适配器则意味着改变一个已有对象的接口。
装饰者模式增强了其它对象的功能而同时又不改变它的接口,因此它对应程序的透明性比适配器要好,其结果是装饰者支持递归组合,而纯粹使用适配器则是不可能的。
代理模式在不改变它的接口的条件下,为另外一个对象定义了一个代理。
组合模式(Composite)将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。
常见的场景有asp.net里的控件机制(即control里可以包含子control,可以递归操作、添加、删除子control),类似的还有DOM的机制,一个DOM节点可以包含子节点,不管是父节点还是子节点都有添加、删除、遍历子节点的通用功能。所以说组合模式的关键是要有一个抽象类,它既可以表示子元素,又可以表示父元素。
模板方法(TemplateMethod)定义了一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模板方法是一种代码复用的基本技术,在类库中尤为重要,因为他们提取了类库中的公共行为。模板方法导致一种反向的控制结构,这种结构就是传说中的“好莱坞法则”,即“别找找我们,我们找你
|
|
模板方法应用于下列情况:
一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现
各子类中公共的行为应被提取出来并集中到一个公共父类中的避免代码重复,不同之处分离为新的操作,最后,用一个钓鱼这些新操作的模板方法来替换这些不同的代码
控制子类扩展,模板方法只在特定点调用“hook”操作,这样就允许在这些点进行扩展
原型模式(prototype)是指用原型实例指向创建对象的种类,并且通过拷贝这些原型创建新的对象。
|
|
状态模式(State)允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类。
这个地方内容代码太长了
我自己理解,总结一下
桥接模式(Bridge)将抽象部分与它的实现部分分离,使它们都可以独立地变化。
|
|
写了6篇,应该让我们避免的模式,那么不要去话时间记忆不要用的,重点在下篇
只需记住,大叔推荐,让我们记住的模式,就可以了
模式1:原型继承
原型继承是让父对象作为子对象的原型,从而达到继承的目的:
function object(o) {
function F() {
}
F.prototype = o;
return new F();
}
// 要继承的父对象
var parent = {
name: "Papa"
};
// 新对象
var child = object(parent);
// 测试
console.log(child.name); // "Papa"
// 父构造函数
function Person() {
// an "own" property
this.name = "Adam";
}
// 给原型添加新属性
Person.prototype.getName = function () {
return this.name;
};
// 创建新person
var papa = new Person();
// 继承
var kid = object(papa);
console.log(kid.getName()); // "Adam"
// 父构造函数
function Person() {
// an "own" property
this.name = "Adam";
}
// 给原型添加新属性
Person.prototype.getName = function () {
return this.name;
};
// 继承
var kid = object(Person.prototype);
console.log(typeof kid.getName); // "function",因为是在原型里定义的
console.log(typeof kid.name); // "undefined", 因为只继承了原型
记住一句话,prototype是一个对象
function object(o) {
function F() {
}
F.prototype = o;
return new F();
}
// 要继承的父对象
var parent = {
name: "Papa"
};
// 新对象
var child = object(parent);
// 测试
console.log(child.name); // "Papa"
// 父构造函数
function Person() {
// an "own" property
this.name = "Adam";
}
// 给原型添加新属性
Person.prototype.getName = function () {
return this.name;
};
// 创建新person
var papa = new Person();
// 继承
var kid = object(papa);
console.log(kid.getName()); // "Adam"
// 父构造函数
function Person() {
// an "own" property
this.name = "Adam";
}
// 给原型添加新属性
Person.prototype.getName = function () {
return this.name;
};
// 继承
var kid = object(Person.prototype);
console.log(typeof kid.getName); // "function",因为是在原型里定义的
console.log(typeof kid.name); // "undefined", 因为只继承了原型
模式2:复制所有属性进行继承
这种方式的继承就是将父对象里所有的属性都复制到子对象上,一般子对象可以使用父对象的数据。
先来看一个浅拷贝的例子:
/* 浅拷贝 */
function extend(parent, child) {
var i;
child = child || {};
for (i in parent) {
if (parent.hasOwnProperty(i)) {
child[i] = parent[i];
}
}
return child;
}
var dad = { name: "Adam" };
var kid = extend(dad);
console.log(kid.name); // "Adam"
var dad = {
counts: [1, 2, 3],
reads: { paper: true }
};
var kid = extend(dad);
kid.counts.push(4);
console.log(dad.counts.toString()); // "1,2,3,4"
console.log(dad.reads === kid.reads); // true
代码的最后一行,你可以发现dad和kid的reads是一样的,也就是他们使用的是同一个引用,这也就是浅拷贝带来的问题。
/* 深拷贝 */
function extendDeep(parent, child) {
var i,
toStr = Object.prototype.toString,
astr = "[object Array]";
child = child || {};
for (i in parent) {
if (parent.hasOwnProperty(i)) {
if (typeof parent[i] === 'object') {
child[i] = (toStr.call(parent[i]) === astr) ? [] : {};
extendDeep(parent[i], child[i]);
} else {
child[i] = parent[i];
}
}
}
return child;
}
var dad = {
counts: [1, 2, 3],
reads: { paper: true }
};
var kid = extendDeep(dad);
kid.counts.push(4);
console.log(kid.counts.toString()); // "1,2,3,4"
console.log(dad.counts.toString()); // "1,2,3"
console.log(dad.reads === kid.reads); // false
kid.reads.paper = false;
这个地方就是厉害在,上面的递归那部分,当包含复合部分就会重新递归调用一次
模式3:混合(mix-in)
混入就是将一个对象的一个或多个(或全部)属性(或方法)复制到另外一个对象,我们举一个例子:
function mix() {
var arg, prop, child = {};
for (arg = 0; arg < arguments.length; arg += 1) {
for (prop in arguments[arg]) {
if (arguments[arg].hasOwnProperty(prop)) {
child[prop] = arguments[arg][prop];
}
}
}
return child;
}
var cake = mix(
{ eggs: 2, large: true },
{ butter: 1, salted: true },
{ flour: '3 cups' },
{ sugar: 'sure!' }
);
console.dir(cake);
模式4:借用方法
一个对象借用另外一个对象的一个或两个方法,而这两个对象之间不会有什么直接联系。不用多解释,直接用代码解释吧:
var one = {
name: 'object',
say: function (greet) {
return greet + ', ' + this.name;
}
};
// 测试
console.log(one.say('hi')); // "hi, object"
var two = {
name: 'another object'
};
console.log(one.say.apply(two, ['hello'])); // "hello, another object"
// 将say赋值给一个变量,this将指向到全局变量
var say = one.say;
console.log(say('hoho')); // "hoho, undefined"
// 传入一个回调函数callback
var yetanother = {
name: 'Yet another object',
method: function (callback) {
return callback('Hola');
}
};
console.log(yetanother.method(one.say)); // "Holla, undefined"
function bind(o, m) {
return function () {
return m.apply(o, [].slice.call(arguments));
};
}
var twosay = bind(two, one.say);
console.log(twosay('yo')); // "yo, another object"
// ECMAScript 5给Function.prototype添加了一个bind()方法,以便很容易使用apply()和call()。
if (typeof Function.prototype.bind === 'undefined') {
Function.prototype.bind = function (thisArg) {
var fn = this,
slice = Array.prototype.slice,
args = slice.call(arguments, 1);
return function () {
return fn.apply(thisArg, args.concat(slice.call(arguments)));
};
};
}
var twosay2 = one.say.bind(two);
console.log(twosay2('Bonjour')); // "Bonjour, another object"
var twosay3 = one.say.bind(two, 'Enchanté');
console.log(twosay3()); // "Enchanté, another object"
模式1:命名空间(namespace)
命名空间可以减少全局命名所需的数量,避免命名冲突或过度。一般我们在进行对象层级定义的时候,经常是这样的:
var app = app || {};
app.moduleA = app.moduleA || {};
// 更简洁的方式
var MYAPP = MYAPP || {};
模式2:定义依赖
有时候你的一个模块或者函数可能要引用第三方的一些模块或者工具,这时候最好将这些依赖模块在刚开始的时候就定义好,以便以后可以很方便地替换掉。
var myFunction = function () {
// 依赖模块
var event = YAHOO.util.Event,
dom = YAHOO.util.dom;
// 其它函数后面的代码里使用局部变量event和dom
};
};
模式3:私有属性和私有方法
JavaScript本书不提供特定的语法来支持私有属性和私有方法,但是我们可以通过闭包来实现,代码如下:
function Gadget() {
// 私有对象
var name = 'iPod';
// 公有函数
this.getName = function () {
return name;
};
}
模式4:Revelation模式
也是关于隐藏私有方法的模式,和《深入理解JavaScript系列(3):全面解析Module模式》里的Module模式有点类似,但是不是return的方式,而是在外部先声明一个变量,然后在内部给变量赋值公有方法。代码如下:
var myarray;
(function () {
var astr = "[object Array]",
toString = Object.prototype.toString;
function isArray(a) {
return toString.call(a) === astr;
}
function indexOf(haystack, needle) {
var i = 0,
max = haystack.length;
for (; i < max; i += 1) {
if (haystack[i] === needle) {
return i;
}
}
return -1;
}
//通过赋值的方式,将上面所有的细节都隐藏了
myarray = {
isArray: isArray,
indexOf: indexOf,
inArray: indexOf
};
} ());
模式5:链模式
链模式可以你连续可以调用一个对象的方法,比如obj.add(1).remove(2).delete(4).add(2)这样的形式,其实现思路非常简单,就是将this原样返回。代码如下:
var obj = {
value: 1,
increment: function () {
this.value += 1;
return this;
},
add: function (v) {
this.value += v;
return this;
},
shout: function () {
console.log(this.value);
}
};
模式6:函数语法糖
函数语法糖是为一个对象快速添加方法(函数)的扩展,这个主要是利用prototype的特性,代码比较简单,我们先来看一下实现代码:
if (typeof Function.prototype.method !== "function") {
Function.prototype.method = function (name, implementation) {
this.prototype[name] = implementation;
return this;
};
}
模式7:对象常量
对象常量是在一个对象提供set,get,ifDefined各种方法的体现,而且对于set的方法只会保留最先设置的对象,后期再设置都是无效的,已达到别人无法重载的目的。实现代码如下:
var constant = (function () {
var constants = {},
ownProp = Object.prototype.hasOwnProperty,
// 只允许设置这三种类型的值
allowed = {
string: 1,
number: 1,
boolean: 1
},
模式8:沙盒模式
沙盒(Sandbox)模式即时为一个或多个模块提供单独的上下文环境,而不会影响其他模块的上下文环境,比如有个Sandbox里有3个方法event,dom,ajax,在调用其中2个组成一个环境的话,和调用三个组成的环境完全没有干扰。
模式9:静态成员
静态成员(Static Members)只是一个函数或对象提供的静态属性,可分为私有的和公有的,就像C#或Java里的public static和private static一样。
我们先来看一下公有成员,公有成员非常简单,我们平时声明的方法,函数都是公有的,比如:
// 构造函数
var Gadget = function () {
};
// 公有静态方法
Gadget.isShiny = function () {
return "you bet";
};
// 原型上添加的正常方法
Gadget.prototype.setPrice = function (price) {
this.price = price;
};
// 调用静态方法
console.log(Gadget.isShiny()); // "you bet"
// 创建实例,然后调用方法
var iphone = new Gadget();
iphone.setPrice(500);
而私有静态成员,我们可以利用其闭包特性去实现,以下是两种实现方式。
第一种实现方式:
var Gadget = (function () {
// 静态变量/属性
var counter = 0;
// 闭包返回构造函数的新实现
return function () {
console.log(counter += 1);
};
} ()); // 立即执行
var g1 = new Gadget(); // logs 1
var g2 = new Gadget(); // logs 2
var g3 = new Gadget(); // logs 3
回调函数
在JavaScript中,当一个函数A作为另外一个函数B的其中一个参数时,则函数A称为回调函数,即A可以在函数B的周期内执行(开始、中间、结束时均可)。
配置对象
如果一个函数(或方法)的参数只有一个参数,并且参数为对象字面量,我们则称这种模式为配置对象模式。例如,如下代码:
var conf = {
username:"shichuan",
first:"Chuan",
last:"Shi"
};
返回函数
返回函数,则是指在一个函数的返回值为另外一个函数,或者根据特定的条件灵活创建的新函数,示例代码如下:
var setup = function () {
console.log(1);
return function () {
console.log(2);
};
};
// 调用setup 函数
var my = setup(); // 输出 1
my(); // 输出 2
// 或者直接调用也可
setup()();
强调一句,这种形式的this,认为是ao,激活对象,也可以认为是null,因此是global
偏应用
忽略
Currying
Currying是函数式编程的一个特性,将多个参数的处理转化成单个参数的处理,类似链式调用。
忽略
立即执行的对象初始化
该模式的意思是指在声明一个对象(而非函数)的时候,立即执行对象里的某一个方法来进行初始化工作,通常该模式可以用在一次性执行的代码上。
({
// 这里你可以定义常量,设置其它值
maxwidth: 600,
maxheight: 400,
// 当然也可以定义utility方法
gimmeMax: function () {
return this.maxwidth + "x" + this.maxheight;
},
// 初始化
init: function () {
console.log(this.gimmeMax());
// 更多代码...
}
}).init(); // 这样就开始初始化咯
分支初始化
分支初始化是指在初始化的时候,根据不同的条件(场景)初始化不同的代码,也就是所谓的条件语句赋值。之前我们在做事件处理的时候,通常使用类似下面的代码:
var utils = {
addListener: function (el, type, fn) {
if (typeof window.addEventListener === 'function') {
el.addEventListener(type, fn, false);
} else if (typeof document.attachEvent !== 'undefined') {
el.attachEvent('on' + type, fn);
} else {
el['on' + type] = fn;
}
},
removeListener: function (el, type, fn) {
}
};
内存优化
该模式主要是利用函数的属性特性来避免大量的重复计算。通常代码形式如下:
var myFunc = function (param) {
if (!myFunc.cache[param]) {
var result = {};
// ... 复杂操作 ...
myFunc.cache[param] = result;
}
return myFunc.cache[param];
};
// cache 存储
myFunc.cache = {};
我们有必要掌握一些OOP基本的特征,并澄清概论中的主要概念。主要讨论封装,继承,多态,接口,那些传统的面向对象概念,可以忽略不看,不影响
总结如下:
1. 原始值类型
回头来看6中用于ECMAScript程序的数据类型,前5种是原始值类型,包括Undefined、Null、Boolean、String、Number、Object。
2. 有必要需要注意的是规范还区分了这内置对象、元素对象和宿主对象。
所有ECMAScript实现的对象都是原生对象(其中一些是内置对象、一些在程序执行的时候创建,例如用户自定义对象)。内置对象是原生对象的一个子集、是在程序开始之前内置到ECMAScript里的(例如,parseInt, Match等)。所有的宿主对象是由宿主环境提供的,通常是浏览器,并可能包括如window、alert等。
也有对象是由特殊的内置构造函数创建: Function(函数对象构造器)、Array(数组构造器) RegExp(正则表达式构造器)、Math(数学模块)、 Date(日期的构造器)等等,这些对象也是Object对象类型的值
3. 字面量Literal
对于三个对象的值:对象(object),数组(array)和正则表达式(regular expression)
4. 然而,如果我们彻底改变函数的prototype属性(通过分配一个新的对象),那原始构造函数的引用就是丢失,这是因为我们创建的对象不包括constructor属性:
function A() {}
A.prototype = {
x: 10
};
var a = new A();
alert(a.x); // 10
alert(a.constructor === A); // false!
因此,对函数的原型引用需要手工恢复:
function A() {}
A.prototype = {
constructor: A,
x: 10
};
var a = new A();
alert(a.x); // 10
alert(a.constructor === A); // true
5. a.[[Prototype]] ----> Prototype <---- A.prototype
此外, 实例的[[Prototype]]值确实是在构造函数的prototype属性上获取的。
然而,提交prototype属性不会影响已经创建对象的原型(只有在构造函数的prototype属性改变的时候才会影响到),就是说新创建的对象才有有新的原型,而已创建对象还是引用到原来的旧原型(这个原型已经不能被再被修改了)。
对象的原型是对象的创建的时候创建的,并且在此之后不能修改为新的对象,如果依然引用到同一个对象,可以通过构造函数的显式prototype引用,对象创建以后,只能对原型的属性进行添加或修改。
这个地方,原型链,特别难理解,详细的看第一篇,观后感的,原型链的,链接,那个是非常详细的说明
6. 有误解:
if (foo instanceof Foo) {
...
}
这不是用来检测对象foo是否是用Foo构造函数创建的,
所有instanceof运算符只需要一个对象属性——foo.[[Prototype]],在原型链中从Foo.prototype开始检查其是否存在。
7. alert(1..toString()); // "1",不是语法错误
大部分程序里使用原型是用来存储对象的方法、默认状态和共享对象的属性。
很多程序员都确信在JavaScript中(甚至其它一些语言),对象是按引用传参,而原始值类型按值传参
1. 按值传递
按值传递,很多开发人员都很了解了,参数的值是调用者传递的对象值的拷贝(copy of value),函数内部改变参数的值不会影响到外面的对象(该参数在外面的值),一般来说,是重新分配了新内存(我们不关注分配内存是怎么实现的——也是是栈也许是动态内存分配),该新内存块的值是外部对象的拷贝,并且它的值是用到函数内部的。
2. 按引用传递
另外一个众所周知的按引用传递接收的不是值拷贝,而是对象的隐式引用,如该对象在外部的直接引用地址。函数内部对参数的任何改变都是影响该对象在函数外部的值,因为两者引用的是同一个对象,也就是说:这时候参数就相当于外部对象的一个别名。
3. 按共享传递(Call by sharing)
最重要的区别就是:函数内部给参数重新赋新值不会影响到外部的对象(和上例按引用传递的case),但是因为该参数是一个地址拷贝,所以在外面访问和里面访问的都是同一个对象(例如外部的该对象不是想按值传递一样完全的拷贝),改变该参数对象的属性值将会影响到外部的对象。
4. 现在我们知道了ECMAScript中将对象作为参数传递的策略了——按共享传递:修改参数的属性将会影响到外部,而重新赋值将不会影响到外部对象
传递的是引用的拷贝(地址副本)
再强调一下,这里所说对象的值是地址(address),而不是对象结构本身,将变量赋值给另外一个变量——是赋值值的引用。因此两个变量引用的是同一个内存地址。下一个赋值却是新地址,是解析与旧对象的地址绑定,然后绑定到新对象的地址上,这就是和按引用传递的最重要区别。
总结如下:
1. 题目1
if (!("a" in window)) {
var a = 1;
}
alert(a);
答案是undefined
2. var a = 1,
b = function a(x) {
x && a(--x);
};
alert(a);
答案1
3. function a(x) {
return x * 2;
}
var a;
alert(a);
答案 a函数
4. function b(x, y, a) {
arguments[2] = 10;
alert(a);
}
b(1, 2, 3);
答案是10
5. function a() {
alert(this);
}
a.call(null);
答案是window
没看懂,基本不用看
稍微看懂了,估计是因为java看多了,ioc,基本不用看
最重要的是Element, Text, Document。
Element节点在页面里展示的是一个元素,所以如果你有段落元素(<p>),你可以通过这个DOM节点来访问。
Text节点在页面里展示的所有文本相关的元素,所以如果你的段落有文本在里面的话,你可以直接通过DOM的Text节点来访问这个文本
Document节点代表是整个文档,它是DOM的根节点。
nodeType类型,1是元素,2是属性,3是text节点,详细的type类型可以通过此地址:
Node.ELEMENT_NODE == 1
Node.ATTRIBUTE_NODE == 2
Node.TEXT_NODE == 3
Node.CDATA_SECTION_NODE == 4
Node.ENTITY_REFERENCE_NODE == 5
Node.ENTITY_NODE == 6
Node.PROCESSING_INSTRUCTION_NODE == 7
Node.COMMENT_NODE == 8
Node.DOCUMENT_NODE == 9
Node.DOCUMENT_TYPE_NODE == 10
Node.DOCUMENT_FRAGMENT_NODE == 11
Node.NOTATION_NODE == 12
node节点的2种类型,一种是元素节点,一种是text节点,上一章节已经列出了所有的节点类型,这两种需要我们现在特别注意。创建元素可以通过createElement方法,而创建text节点可以使用createTextNode,相应代码如下:
正如我们上章所说的,DOM和JavaScript语言是2个单独的东西,浏览器事件是DOM API的一部分,而不是JavaScript的一部分。
‘mouseover’ – 鼠标移动到某元素上的时候触发mouseover事件。
‘mouseout’ – 鼠标从某元素离开的时候触发mouseout事件。
‘mousemove’ – 鼠标在某元素上移动但未离开的时候触发mousemove事件。
‘change’ – 控件失去input焦点的时候触发该事件(或者值被改变的时候)。
‘load’ – 页面加载完毕(包括内容、图片、frame、object)的时候触发该事件。
‘resize’ – 页面大小改变的时候触发该事件(例如浏览器缩放)。
‘scroll’ – 页面滚动的时候触发该事件。
‘unload’ – 从页面或frame删除所有内容的时候触发该事件(例如离开一个页面)。
严格来说,有2中不同的模型:W3C模型和微软模型,除IE之外W3C模型支持所有的现代浏览器,而微软模型只支持IE
使用W3C模型的代码如下:
// 格式:target.addEventListener( type, function, useCapture );
// 例子:
var myIntro = document.getElementById('intro');
myIntro.addEventListener('click', introClick, false);
使用IE模型的代码如下:
// 格式: target.attachEvent ( 'on' + type, function );
// 例子:
var myIntro = document.getElementById('intro');
myIntro.attachEvent('onclick', introClick);
一个非常重要的内容是Event对象,当事件发生的时候出发某个函数,该Event对象将自动在函数内可用,该对象包含了很多事件触发时候的信息,但IE却没有这么实现,而是自己实现的,IE浏览器是通过全局对象window下的event属性来包含这些信息
例如当你想取消默认的行为的时候你可以使用Event对象里的preventDefault()方法,但IE里不得不使用对象的returnValue属性值来控制
事件冒泡,就是事件触发的时候通过DOM向上冒泡,首先要知道不是所有的事件都有冒泡。事件在一个目标元素上触发的时候,该事件将触发一一触发祖先节点元素,直到最顶层的元素:
如图所示,如果a连接被点击,触发触发连接的click事件,然后触发p的click事件,以此再触发div和body的click事件。顺序不变,而且不一定是在同时触发的。
举例来说,如果你有一个很多行的大表格,在每个<tr>上绑定点击事件是个非常危险的想法,因为性能是个大问题。流行的做法是使用事件委托。事件委托描述的是将事件绑定在容器元素上,然后通过判断点击的target子元素的类型来触发相应的事件。
事件委托依赖于事件冒泡,如果事件冒泡到table之前被禁用的话,那上面的代码就无法工作了。
经典的事件处理模型,微软的冒泡,以及网景的捕获模型,w3c的两者兼容,网上例子很多,去看解释
内容不多,但是作为从头规范开始,值得遵循,现在看来,才更能明白里面的道理。
总结一下:
1. 避免全局变量,为啥?自己去看,以及隐式全局变量的副作用,那怎么办?命名空间
2. 推荐单一的单var形式,如 var a = 1,b = 2,myobject = {};形式
3. for循环,预存缓存变量,for-in遍历对象用hasOwnProperty,去掉原型链的属性
4. 避免使用eval,记住该咒语“eval()是魔鬼”,给setInterval(), setTimeout()和Function()构造函数传递字符串,大部分情况下,与使用eval()是类似的,因此要避免
5. 编码规范,缩进,空格,花括号,分号
6. 命名函数,啥时候大写,啥时候小写,推荐驼峰命名
7. 关于注解,通常,当你深入研究一个问题,你会很清楚的知道这个代码是干嘛用的,但是,当你一周之后再回来看的时候,想必也要耗掉不少脑细胞去搞明白到底怎么工作的。就是保持注释的及时更新,因为过时的注释比没有注释更加的误导人。
总结如下:
1. 什么是申明函数?
2. 什么是表达式函数?
3. 什么是函数语句?
4. 什么是命名函数?
5. 主要是区别的地方,很细微,然后又很多浏览器的怪异行为,坑比较多
6. 还有一种函数表达式不太常见,就是被括号括住的(function foo(){}),他是表达式的原因是因为括号 ()是一个分组操作符,它的内部只能包含表达式
7. 后面的部分,如果第一次看,肯定看不懂,是啥意思,建议先忽略掉
总结如下:
1. 正常的module模式,缺点就是,每次都必须new,每个实例都是单独的
|
|
2. 改进过后的,利用函数自执行,可以直接使用
|
|
3. 改進松耦合
|
|
4. 克隆
|
|
5. 子模块
|
|
1. 自動執行
(function () { /* code */ } ()); // 推荐使用这个
2. (function () { /* code */ })(); // 但是这个也是可以用的,括號
3.在一个表达式后面加上括号(),该表达式会立即执行,但是在一个语句后面加上括号(),是完全不一样的意思,他的只是分组操作符。
4. 闭包的经典例子
|
|
变量相对于简单属性来说,变量有一个特性(attribute):{DontDelete},这个特性的含义就是不能用delete操作符直接删除变量属性。
但是这个规则在有个上下文里不起走样,那就是eval上下文,变量没有{DontDelete}特性。
1,一个函数上下文中确定this值的通用规则如下:
2. 在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。如果调用括号()的左边是引用类型的值,this将设为引用类型值的base对象(base object),在其他情况下(与引用类型不同的任何其它属性),这个值为null。不过,实际不存在this的值为null的情况,因为当this的值为null的时候,其值会被隐式转换为全局对象。
3. 我们可以很明确的告诉你,为什么用表达式的不同形式激活同一个函数会不同的this值,答案在于引用类型(type Reference)不同的中间值。
4. 标识符是变量名,函数名,函数参数名和全局对象中未识别的属性名
5. (function () {
alert(this); // null => global
})();
6. 函数调用中手动设置this,apply,call
7. 作为构造器调用的函数中的this,都将this的值设置为新创建的对象。
8. 引用类型和this为null,默认为golbal
1. 函数上下文的作用域链在函数调用时创建的,包含活动对象和这个函数内部的[[scope]]属性。
2. 注意这重要的一点--[[scope]]在函数创建时被存储--静态(不变的),永远永远,直至函数销毁
3. 闭包理解
var x = 10;
function foo() {
alert(x);
}
(function () {
var x = 20;
foo(); // 10, but not 20
})();
[[Scope]]包括在函数内创建的词法作用域(父变量对象)。当函数进一步激活时,在变量对象的这个词法链(静态的存储于创建时)中,来自较高作用域的变量将被搜寻。
此外,这个例子也清晰的表明,一个函数(这个例子中为从函数“foo”返回的匿名函数)的[[scope]]持续存在,即使是在函数创建的作用域已经完成之后。
4. 通过函构造函数创建的函数的[[scope]]属性总是唯一的全局对象
5. 源于ECMAScript 的原型特性。如果一个属性在对象中没有直接找到,查询将在原型链中继续。即常说的二维链查找。(1)作用域链环节;(2)每个作用域链--深入到原型链环节
6. 全局和eval上下文中的作用域链,全局上下文的作用域链仅包含全局对象
7. 在代码执行阶段有两个声明能修改作用域链。这就是with声明和catch语句。它们添加到作用域链的最前端,对象须在这些声明中出现的标识符中查找。
1。 只有这2个位置可以声明函数,也就是说:不可能在表达式位置或一个代码块中定义它。
2. // 函数可以在如下地方声明:
// 1) 直接在全局上下文中
function globalFD() {
// 2) 或者在一个函数的函数体内
function innerFD() {}
}
3. 相当一部分问题出现了,我们为什么需要函数表达式?答案很明显——在表达式中使用它们,”不会污染”变量对象。最简单的例子是将一个函数作为参数传递给其它函数。
4. 这种模式中,初始化的FE的名称通常被忽略:
(function () {
// 初始化作用域
})();
5. ”为何在函数创建后的立即调用中必须用圆括号来包围它?”,答案就是:表达式句子的限制就是这样的。
6. function () {
...
}();
// 即便有名称
function foo() {
...
}();
如果在全局代码里定义(也就是程序级别),解释器会将它看做是函数声明,因为他是以function关键字开头,第一个例子,我们会得到SyntaxError错误,是因为函数声明没有名字(我们前面提到了函数声明必须有名字)。
第二个例子,我们有一个名称为foo的一个函数声明正常创建,但是我们依然得到了一个语法错误——没有任何表达式的分组操作符错误。在函数声明后面他确实是一个分组操作符,而不是一个函数调用所使用的圆括号。
var z = 10;
function foo() {
alert(z);
}
foo(); // 10 – 使用静态和动态作用域的时候
(function () {
var z = 20;
foo(); // 10 – 使用静态作用域, 20 – 使用动态作用域
})();
// 将foo作为参数的时候是一样的
(function (funArg) {
var z = 30;
funArg(); // 10 – 静态作用域, 30 – 动态作用域
})(foo);
上述描述的就是两类funarg问题 —— 取决于是否将函数以返回值返回(第一类问题)以及是否将函数当函数参数使用(第二类问题)。
为了解决上述问题,就引入了 闭包的概念。
这里说明一下,开发人员经常错误将闭包简化理解成从父上下文中返回内部函数,甚至理解成只有匿名函数才能是闭包。
再说一下,因为作用域链,使得所有的函数都是闭包(与函数类型无关: 匿名函数,FE,NFE,FD都是闭包)。
ECMAScript中,闭包指的是:
从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
从实践角度:以下函数才算是闭包:
即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
在代码中引用了自由变量
顺便提下,函数对象的 apply 和 call方法,在函数式编程中也可以用作应用函数。 apply和call已经在讨论“this”的时候介绍过了;这里,我们将它们看作是应用函数 —— 应用到参数中的函数(在apply中是参数列表,在call中是独立的参数):
闭包还有另外一个非常重要的应用 —— 延迟调用:
var a = 10;
setTimeout(function () {
alert(a); // 10, after one second
}, 1000);
还可以创建封装的作用域来隐藏辅助对象:
var foo = {};
// 初始化
(function (object) {
var x = 10;
object.getX = function _getX() {
return x;
};
})(foo);
alert(foo.getX()); // 获得闭包 "x" – 10
记得十年前,这个时候,已经进入了考场,转眼,十年都过去了
每个系统都有日志记录,而多数都是用的log4j,以为会配置了,懂了,发现还是有些细节的问题,要问,说不上来
今天专程来补图,喵帕斯~
祝天下所有的大小朋友,儿童节快乐~