面向对象编程
封装
对象生成的原始模式
var Cat = {
name : '',
color : ''
}
然后生成实例对象,
var cat1 = {};
cat1.name = '大毛';
cat1.color = '黄色';
var cat2 = {};
cat2.name = '二毛';
cat2.color = '黑色';
很简单,但问题是,这样生成的“实例对象”其实和原型对象之间基本没有任何联系。而且生成多个实例的时候,没有简洁的方法,会非常麻烦。
原始模式的改进
function Cat(name,color){
return {
name : name,
color : color
}
}
然后生成实例对象,但其实就等于在调用函数,返回一个对象。
var cat1 = Cat('大毛','黄色');
var cat2 = Cat('二毛','黑色');
这种问题是,如果 Cat 中不进行额外的处理,这里仍旧不能反映出来 cat1 和 cat2 内在的联系。
构造函数模式
function Cat(name,color){
this.name = name;
this.color = color;
}
现在就可以生成实例对象了。
var cat1 = new Cat('大毛','黄色');
var cat2 = new Cat('二毛','黑色');
这时 cat1 和 cat2 会自动含有一个 constructor 属性,指向他们的构造函数。
console.log(cat1 instanceof Cat); //true
console.log(cat2 instanceof Cat); //true
但此时这种方式仍旧存在其固有的问题。假设这里有一个方法。
function Cat(name,color){
this.name = name;
this.color = color;
this.type = '猫科动物';
this.eat = function(){console.log('吃老鼠');}
}
生成实例
var cat = Cat('大毛','黄色');
console.log(cat.type); // 猫科动物
cat.eat(); // 吃老鼠。
表面上没什么问题,用的时候也很好用,但是假如我们需要生成大量的 Cat 的实例,那么每个实例都会有 type 属性和 eat 方法,而且这两个都是一摸一样的内容,导致了内存的浪费,因为此时两个实例的相同属性和方法,并不是同样的内存地址。会存在多份。
那么,能不能将所有的相同的属性和方法只存在一份,然后每个实例同样的也可以使用呢?答案肯定是可以的。
prototype 模式
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.type = '猫科动物';
cat.prototype.eat = function(){console.log('吃老鼠');}
这时候,
var cat1 = new Cat('大毛','黄色');
var cat2 = new Cat('二毛','黑色');
此时,再多的实例对象,其 type 属性和 eat 方法都指向的是同一份,也即相同的内存地址。
console.log(cat1.eat === cat2.eat); //true
prototype 模式的验证方法
为了配合 prototype 模式, javascript 定义了一些辅助方法,帮助我们更方便的使用它。
isPrototypeOf()
这个方法用来判断,某个 prototype 对象和某个实例之间的关系。
console.log(Cat.prototype.isPrototypeOf(cat1)); //true
hasOwnProperty()
次方法用来判断某个属性到底是prototype 对象的属性还是类定义的(本地属性)。
console.log(cat1.hasOwnProperty('name')); true
console.log(cat1.hasOwnProperty('type')); false
in 运算符
此运算符用来判断某个属性是不是存在于某个实例中,即某个实例是否包含某个属性,不管是本地还是继承自 prototype
console.log("name" in cat1); // true
console.log('type' in cat1); // true
console.log('typo' in cat1); // false
in 运算符还可以用来遍历对象的属性。
for(var key in cat1){
console.log('cat1[' + key ' ]=' + cat1[key]);
}
当然在遍历的时候可以使用 hasOwnProperty() 来限制输出的属性。
兼容多种模块规范的 javascript 模块化写法
;(function(name,definition){
var hasDefine = typeof define === 'function', // 检测上下文环境是否为 AMD 或者 CMD
hasExports = typeof module !== 'undefined' && module.exports; // 检查上下文是否为 node
if(hasDefine){
define(definition); // AMD 或者 CMD 环境
}else if(hasExports){
module.exports = definition(); // 定义为普通 node 模块
}else{
this[name] = definition(); // 将模块的执行结果挂载在 window 变量中,在浏览器中 this 指向 window 变量。
}
})('hello',function(){ // 此处 hello 只是作为模块化的示例名称
var hello = function(){};
return hello;
})
不过此种写法要保证前端引用或者后端使用没有全局的 define 函数覆盖 AMD 或者 CMD 的 define 函数。
此代码来源于 深入浅出Node.js P45 2.7.4,此处为记录以及熟悉。
sort 作为数组的方法,被定义在 Array.prototype.sort,也即在 JavaScript 中所有数组都可以使用 sort 方法进行排序。虽然一般情况下,sort 甚至可以不用传递排序函数一样可以正常工作,但很多情况下,我们还是必须明确的定义排序函数,才能更好的按我们的需求来工作,这也是一般情况下建议的方式,因为不知道不同的 js 引擎在不传递排序函数的时候,会按照什么方案进行排序。
例如:
var colors = ['red', 'green', 'blue', 'yellow', 'white'];
console.log(colors.sort()); // ["blue", "green", "red", "white", "yellow"]
console.log(colors); // ["blue", "green", "red", "white", "yellow"]
如果不传递排序函数,那么,对于字符串来说,其将按照字母顺序来进行排序,可是,如果这些字符串里面有大写小写字母混合呢?
var colors = ['red', 'green', 'blue', 'Yellow', 'White'];
console.log(colors.sort()); // ["White", "Yellow", "blue", "green", "red"]
怎么样,结果不一样了吧?这是因为此时按照 ASCII 编码的顺序进行排序了。
同时,另一个问题是,sort 函数作用于原数组,也即是排序的结果会覆盖排序前的数组。
然后,对于数字数组,不传递排序函数的话,会是什么结果呢?
var money = [12, 3, 7.4, 200];
console.log(money.sort()); // [12, 200, 3, 7.4]
咦?并没有得到预期的结果(除非你的预期不是按数字大小),那么为什么呢?
因为在 sort 排序的时候,如果不传递排序函数,那么会先将所有的元素转换为 字符串 ,然后才会一个字一个字(ASCII,对于 true 或者 false 这类型的值都是一样的)的排序,为什么都转换成字符串,因为 JavaScript 数组中可以是任意类型的数据,所以 JavaScript 无法推断到底转换为什么类型去排序。
那么我们看看不同的数据类型,排序函数的一些写法:
数组内都是数字,或者期望按数字为主的排序方式。
var money = [12, 3, 7.4, 200];
var compare = function(a, b) {return a - b;};
console.log(money.sort(compare)); // [3, 7.4, 12, 200]
//console.log(money.sort(function(a, b) {return a - b;})); // [3, 7.4, 12, 200]
这是最基础的排序函数,任何讲到 JavaScript 排序的地方,基本都会讲这个。首先取前两个元素赋值给 a b,返回 a-b 的值,如果返回负值则 a 在 b 前,等于 0 则无所谓,因为两个元素相等,返回正直,则 a 在 b 后。
对于字符串的元素来说,和数字的差不多,无非就是不同的排序函数,那如果是对象呢?
var people = [
{
name: 'Alice',
id: 1234
},
{
name: 'Bob',
id: 567
}
];
var compare = function(a, b) {return a.id - b.id;}
console.log(people.sort(compare)); // Bob is before Alice now
对于元素的排序函数传递的参数都一样,两个数组中的元素,然后此处主要比较两个对象元素的 id 的值,来进行排序的。
可是如果如下面这种情况呢?
var everything = [4, 'Red', '$200', 'white', 7.4, 12, true, 0.3, false];
var compare = function(a, b) {return a - b;};
console.log(everything.sort(compare));
当排序函数进行计算的时候,由于有减法运算的参与,所以这里都会先转成数字进行计算的,但是如果是字符串的话,此处就会转换为 NaN,因为在 ECMA 规范中没有明确定义这时候应该如何进行排序,所以各个平台,也就是各个浏览器(JavaScript环境)的不同,导致最终的结果会不一定相同。大家可以自己在不同的浏览器上测试下。
例子来源:http://zhangwenli.com/blog/2013/11/02/sorting-in-javascript/
一般情况下,我们都使用 JavaScript 的 console.log() 进行 debug 但是,对于 Object 来说,每次 console.log 之后,输出的都是不是十分有好的数据,例如:
var languages = [
{ name: "JavaScript", fileExtension: ".js" },
{ name: "TypeScript", fileExtension: ".ts" },
{ name: "CoffeeScript", fileExtension: ".coffee" }
];
console.log(languages);
输出的就如同下图所示(当然,会根据不同的浏览器以及不同的版本会有所差别):
然后呢,其实看起来还是那么回事,虽然麻烦点,但是还可以看的清楚,到底什么是什么东西。
再然后呢?如果我们使用这里介绍的 console.table 呢?
不过,聪明的人一眼就看出来其中的玄机了,或者,看到这里,直接就去浏览器的console里面,执行这句代码就完了。怎么样?很好用吧?
不过还是给个例子比较好,因为大部分都比较懒嘛。
还是上边的对象,然后使用 console.table(language) 输出看看。
看到了吧,多么的一目了然!
然后点击各个表头试试看,还可以排序哦。
好了,上面是数组,但是其实对于对象也是可以使用的哦。
var languages = {
csharp: { name: "C#", paradigm: "object-oriented" },
fsharp: { name: "F#", paradigm: "functional" }
};
console.table(languages);
然后看看结果,看到了吧?
可是,如果就这么点,那么除了更好的阅读方式之外,就没什么意思。
那么,它还可以过滤,哈哈。
这么:
console.table(languages, ["name", "paradigm"]); //多个 key 组成的数组
或者这么:
console.table(languages, "name”); //单个 key
很多内容来自:http://blog.mariusschulz.com/2013/11/13/advanced-javascript-debugging-with-consoletable
前几天,在 Twitter 看到一句非常整洁的代码:
var bind = Function.prototype.call.bind(Function.prototype.bind);
第一次看的时候,我可以知道这段代码想干嘛,它让 x.y(z) 变成 y(x,z). 带着非常愉快的心情,我把这段代码给我的学生们看,但是,他们问我这段代码干嘛用呢。然后我就开始解释,但是我不知道从何说起,然后我就转身走开了。
应该明白的是,大部分比较好的代码,应该直接告诉我们它们是用来做什么的,带着这些在函数式 JavaScript 领域的经验,并且也在不断的阅读函数式 JavaScript 相关的书籍,在读的时候,我觉得是没有任何问题的,但是解释,将这些函数式程序解释给别人来说,还是没有太多经验。
但是,我还是想尝试下,通过我的方式,一些简单的示例,还有大量的注释,来解释一下这段代码的意思。
定义一个简单的对象 context
var context = {foo : “bar”};
定义一个函数,使用了一个在 this 上下文中,被命名为 foo 的变量,
function returnFoo(){
return this.foo;
}
这个变量不存在于作用域中,因此,其返回 undefined。
(具体解释为,此时, this 指代全局的 window,而 window.foo 没有定义,所以返回 undefined)
returnFoo(); // undefined
但是,如果我们将此函数绑定到某个上下文。
var bound = returnFoo.bind(context);
现在变量 foo 存在于作用域中了.
(即,在 context 这个上下文中,是有定义 foo 变量的)
bound(); // bar
这就是 Function.prototype.bind 的作用,因为 returnFoo 是个函数,所以其继承了 function 的 prototype。
除了这种方式,还有另外其他方式来改变一个函数的上下文。例如 call 和 apply。
returnFoo.call(content); // bar
returnFoo.apply(content); // bar
还有就是将函数附加给某个对象,作为某个对象的一个方法。
content.returnFoo = returnFoo;
content.returnFoo(); // bar
让我们再来看看其他一些使用方法
Array.prototype 有一个特别好用的方法,叫做 slice。
在数组上调用此方法,它会返回一个从开始到结束的这个数组的一个拷贝,例如:
[1,2,3].slice(0,1); // [1]
现在将,slice 赋值给一个本地变量。
var slice = Array.prototype.slice;
slice 现在未被绑定到任何对象,所以它和 Array.prototype.slice 比起来,对提供给它的上下文,或者 this,是不能正常的工作。
slice(0,1); //typeError
slice([1,2,3],0,1); //TypeError
但是,如果我们通过 call 或者 apply 来调用它,并且给它提供一个上下文。
slice.call([1,2,3],0,1); // [1]
slice.apply([1,2,3],[0,1]); //[1]
那么对于 bind 呢?我们来试试。
slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1,2,3],0,1); //[1]
很酷吧,那么我们对 bind 使用和 slice 一样的方式。
var bind = Function.prototype.call.bind(Function.prototype.bind);
var content = {foo : ”bar”};
function returnFoo(){
return this.foo;
}
var amazing = bind(returnFoo, content);
amazing(); //bar
这样,就明白了 这段代码的意图。
这个bind 的解释是来源于 MDN 的,但是不明白为什么 Google 对 MDN 的东西这么低的评价,所以要翻页才能找到的。