前言
由于最近的项目用到了一些js的代码,所以我带着好奇心,认真阅读了这本书,粗略地了解js语言的基本结构和特性,对于一些不熟悉的新概念,以记录的形式加强印象,也是对学习的反思总结。
一、字面量(literals)
“字面量“这个词在书中高频出现,先看看他的定义:
字面量表示如何表达这个值,一般除去表达式,给变量赋值时,等号右边都可以认为是字面量。
举个简单的例子就明白了,例如
var a = 1;
var b = "hello world";
var c = [];
var d = {};
var e = function (a,b){return a + b;}
以上例子中,赋值号右边都是字面量,分别是数字字面量,字符串字面量,数组字面量,对象字面量及函数字面量。
为什么要有字面量呢?
我的理解,js和php一样都是弱类型语言,需要根据赋值号右边的内容来判断变量的类型,而字面量就是为了很方便地按照指定规格创建新的对象,数组或者定义函数值,让我们更清楚地知道我到底定义了一个什么。
字面量与变量的区别?
- 字面量都是一些不可改变的值,比如 :1 2 3 4 5
- 变量就像一个容器,可以用来保存字面量,而且变量的值是可以任意改变的
- 通常我们不会直接调用字面量,而是先将字面量存到变量中,再进行调用
二、对象(object)
真的应了老师说的,”万物皆对象“,在js中,没有类,有的只有对象,数组是对象,函数是对象,正则表达式是对象,当然,对象也是对象。
对象字面量(object literals)
写法很简单,就是一对花括号括住“属性:值”对,这里就不再举例子了,对象是可以嵌套的,用点运算符来获取对象的某个值
引用(reference)
对象不会复制,通过引用来传递,例如
var stooge = {
name : "Jack";
};
var x = stooge;
x.name = "Tom";
var result = stooge.name;
由于x与stooge指向同一个对象,所以此时result为”Tom“.
原型(prototype)
js里,每一个对象都会连接一个原型对象,并且可以继承属性,所有通过字面量对象创建的对象都会连接到Object.prototype,它是js中标配对象
当创建一个对象时,可以选择一个对象作为它的原型,,这有点像类的继承,具体实现的方法书中已经帮我们写好了,直接拿来套用就行
var stooge = {
name : "Tom"
};
if (typeof Object.beget !== 'function') {
Object.create = function(o){
var F = function () {};
F.prototype = o;
return new F();
};
}
var another_stooge = Object.create(stooge);//another_stooge选择stooge作为它的原型
关于原型,有几点需要注意
- 对一个对象做出改变时,不影响它的原型,但改变原型的属性时,继承它的对象的属性也会改变,但不会改变继承以后新增的属性
- 在原型之中添加属性,继承它的所有对象也会可见,改变原型的一个属性,继承它的对象的属性也会改变,删除原型的一个属性,继承它的对象也会删除
- 如果b继承a,c继承b,那么就会形成原型链,如果获取一个c中没有的属性,那么它会从b中找这个属性,如果b中也没有,它会从a中找,如果a中还没有,则返回undefined,此过程称为委托
关于第二条我有一个疑问,在原型中添加一个属性,继承它的对象是否也添加了这个属性?
如果我直接检索一个对象的属性,由于委托的存在而使得我无法确认该属性是对象还是原型的
例如,我在上面的代码继续写
stooge.lastname = "jack";
var name = another_stooge.lastname;
document.writeln(name);
这时,输出结果是jack,但这个jack是another_stooge的还是stooge的,我并不知道。
这个其实在后面给出了解决方法,就是使用hasOwnProperty()这个方法
stooge.lastname = "jack";
var status = another_stooge.hasOwnProperty('lastname');
document.writeln(status);
运行结果:false
这个方法会检查对象是否拥有独有属性,而且不会检查原型链,如果是,返回true,否则返回false
枚举
使用typeof可以检查对象属性的类型,用来过滤掉函数,而hasOwnProperty则可以过滤掉不关心的值,那么结合forin语句就可以遍历对象所有的值了
var name;
for (name in another_stooge) {
if (typeof another_stooge.name !== 'function') {
document.writeln(name + ':' another_stooge.name);
}
}
三、函数(Functions)
函数这部分涉及的内容较多,各种调用,回调,级联,记忆等之前从没见过的用法
函数对象(Function Objects)
对象字面量创建对象,它连接到object.prototype
函数字面量创建函数对象,它连接到Function.prototype
有趣的是,函数在创建完之后,会天生自带属性和方法,函数不仅可以当“函数”使用,也能当“对象”使用,函数可以保存到变量,对象,数组中。函数可以当成另一个函数的参数,函数可以返回函数,函数甚至可以拥有函数(注意,不是在一个函数中调用另一个函数,而是函数可以作为另一个函数的属性),当一个函数是另一个对象的属性时,函数被称为方法
四种函数的调用模式
1.方法调用模式
这种调用方法在面向对象编程语言中最为常见,当一个函数保存为一个对象的属性时,可以通过object.function()
进行调用
另外,方法可以通过this指针找到自己的对象,在方法内,可以通过this.another_function(),this.property
使用或修改对象其他的方法和属性,在使用thinkphp过程中,this的使用非常高频,所以这样的用法并不陌生
2.函数调用模式
当函数定义在对象外部时,,通过function(value1,value2,etc)
调用成为函数调用模式
函数调用模式有一个麻烦的地方。
当函数定义在对象内部时,this指调用函数的对象,当函数没有绑定在对象内时,默认指向全局对象(window)
但是当我在函数内定义了一个方法时,this应该指向对象,而实际却指向全局对象,这使得在定义函数内部的方法时不能使用该对象的其他属性辅助完成,例如以下
var ob = {
value : 1,
add : function () {
var show = function () {
return this;
};
return show();
}
};
var result = add();
document.writeln(result);
运行结果:[object Window]
由结果可知,show方法中的this并没有指向ob,而是指向了全局对象。
解决方法:在外部函数内定义一个变量that并赋值为this,内部函数使用that,即可访问该对象
var ob = {
value : 1,
add : function () {
var that = this;
var show = function () {
return that.value;
};
return show();
}
};
var result = ob.add();
document.writeln(result);
运行结果:1
此时,show就可以访问ob.value了
3.构造器调用模式
先上实例
var Person = function(name){
this.name = name;
};
var student = new Person("li");//构造一个Person对象,并将name属性赋值为li
document.writeln(student.name);
很眼熟,像极了类的构造函数,用法也是,直接new Function(value,etc)
这种构造对象的模式对于习惯了基于类的语言的我简直是福音,我看到时满满的亲切感
书上对于构造器调用不是十分推荐,因为当使用此类函数时不加new修饰符会带来意想不到的错误,既没有编译器提醒也没有运行警告。在后面他提出了一个更好的解决办法——伪类,会这在之后进行具体的学习
4.Apply,Call调用模式(上下文调用模式)
书中对这种调用的介绍很少,我结合书和菜鸟教程做了一些自己的总结
Apply和Call都是函数自带的方法,功能一样,用法不同
apply:
- 函数模式
var array = [1,2];
var add = function(number1,number2){
return number1 + number2;
};
var result = add.apply(null,array);//以window作为上下文
document.writeln(result);
2.方法模式
var array = [1,2];
var add = function(number1,number2){
return number1 + number2;
};
var o = { name : "jack"};
var result = add.apply(o,array);//以o作为上下文
document.writeln(result);
两种调用,只是在参数上有所不同,第一个参数为null时,以window作为上下文,如果是对象,则以该对象为上下文
这个上下文是什么?有什么用?
这使我非常困惑,这种调用看似很没用啊!
直到我看到一则实例,我才感受到它的魅力
题目:获得 div 与 p 标签, 并添加边框 border: 1px solid red
一般做法:
var p_list = document.getElementsByTagName('p');//获取所有p标签,存到p_list
var div_list = document.getElementsByTagName('div');//获取所有div标签,存到div_list
var i = 0;
for( ; i < p_list.length; i++ ) {
p_list[ i ].style.border = "1px solid red";//通过for循环,挨个添加样式
}
for( i = 0; i < div_list.length; i++ ) {
div_list[ i ].style.border = "1px solid red";//通过for循环,挨个添加样式
}
使用上下文模式:
var t = document.getElementsByTagName, arr = [];// 伪数组没有 push 方法,所以这里要 声明一个 数组
arr.push.apply( arr, t.apply( document, [ 'p' ] ) );// 其实是把p标签,一个个当成参数放了进来
arr.push.apply( arr, t.apply( document, [ 'div'] ) );//同上
arr.forEach( function( val, index, arr ) {
val.style.border = '1px solid red';//使用foreach,只进行一次遍历,即可添加样式
});
上述实例的核心就是将两个数组合并在一起,进行一次遍历
对于call,只是参数不再以数组形式封装,而是function.call(null,value1,value2,etc)
这样的形式,在此不多做赘述
小结
- 通过js,我首次接触到基于原型继承设计的语言,刚开始学习对于这样的设计不是很明白,也不明白原型设计的好处是什么。但是js的广泛使用证明它必然有它的优势,我无法理解说明我的技术还达不到去理解一个语言精髓的水平
- js中处处体现着万物姐皆对象,处处是对象,这让我在一开始感到费解,但随着进一步的学习,我发现,在js中,对象与其说是对象,不如说是属性和方法的集合,一个对象在定义之初,系统就已经“免费”赠送了这个集合一些属性和方法,以这样的思路去思考,就不难理解为什么函数会是对象了
- 函数的四种调用方法,方法模式和函数模式我都非常熟悉,只是函数调用模式需要注意this与that,构造器模式作者并不推荐,只是作为了解,而上下文模式设计的巧妙我还不能完全领会,基本处于一头雾水。
- javascript语言精粹这本书感觉很多地方过于简略,需要我结合网上的一些资料辅助才能看懂,换言之,它并不适合新手入门,然而即便如此,我也抱着能看懂多少算多少的心态,去一窥这个语言的魅力