gdccwxx 的布洛阁


  • 首页

  • 关于

  • 标签

  • 归档

  • 搜索

JavaScript高级程序设计重读---7章

发表于 2017-07-13

函数表达式

函数表达式和函数声明中,有以下因为函数声明有函数声明提升器,因此大多数用函数声明,但是函数表达式也有其运用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 这种情况可能造成意想不到的情况
if(condition){
function sayHi(){
alert('hi')
}
}else {
function sayHi(){
alert('hello')
}
}
// 应该这样
var sayHi
if(condition){
sayHi = function (){
alert('hi')
}
}else {
sayHi = function (){
alert('hello')
}
}

递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function factorial(num){
if(num <= 1){
return 1
}else {
return num* factorial(num-1)
}
}
// 但是以下情况会出错
var another = factorial
factorial = null
alert(another(4)) // 出错,因为factorial变量设置位null,结果现在factorial已经不再是函数
//通过以下一种情况解决
function factorial(num){
if(num <= 1){
return 1
}else {
return num* arguments.callee(num-1)
}
}
// 上种情况在严格模式下有问题。这种情况可以代替函数名,在严格模式下依然可以生效
var factorail = (function f(num){
if(num <= 1){
return 1
}else{
return num * f(num -1)
}
})

闭包

闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。
首先理解活动对象。活动对象是c语言里面称之为形参。在内存中,形参往往是在当前环境作用域的首位,其次是上一个环境,以此类推。因此其作用域链是由当前环境依次向上。
正常函数是执行完成之后,函数内参数和变量将会清除。因此无法访问该变量。闭包是一种将当前函数返回,导致当前活动对象无法被销毁,到函数外即可取到变量。上代码

1
2
3
4
5
6
7
8
9
10
11
function create(){
var result = new Array();
for(var i = 0;i < 10; i++){
result[i] = function (num){
return function(){
return num
}
}(i)
}
return result
}

此函数可以获取到i的值。通过访问不同的result函数以达到目的
因此可以模仿块级作用域。

1
2
3
(function(){
// 这里是块级作用域
}())

小结

通过这章学习,还是不明白几个模式。但是对闭包有了很大的了解,知道闭包的原理。也明白了闭包的大多数应用场景。对今后的学习更加自信了。

JavaScript高级程序设计重读---6章

发表于 2017-07-13

面向对象的程序设计

属性类型

ECMAScript中有两种属性,数据属性和访问器属性

数据属性

[[Configurable]] : 表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。
[[Enumerable]] : 表示能否通过 for-in 循环返回属性。
[[Writable]] : 表示能否修改属性的值。
[[Value]] : 包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值时,把新值保存在这个位置。默认值是 undefined。

ps:将Configurable修改为false之后,除了Writable可修改之外,Configurable,Enumerable都不可再修改。

Object.defineProperty可以为对象添加属性

访问器属性

[[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。
[[Enumerable]]:表示能否通过 for-in 循环返回属性。
[[Get]]:在读取属性时调用的函数。默认值为 undefined。
[[Set]]:在写入属性时调用的函数。默认值为 undefined。

get,set方法
有时候希望访问属性时能返回一个动态计算后的值, 或希望不通过使用明确的方法调用而显示内部变量的状态.在JavaScript中, 能通过使用 getter 实现. 尽管可能结合使用getter和setter创建一个伪属性,但不能既使用getter绑定到一个属性上,同时又用该属性真实的存储一个值.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var book = {
_year : 2004,
edition : 1
};
Object.defineProperty(book,"year",{
get : function () {
alert(this._year);
},
set : function (newValue) {
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
book.year; // 弹出窗口,显示 2004
book.year = 2005;
console.log(book.edition); // 2

get,set方法可以动态的关注对象中变量的变化,可以使其他变量随着变化而变化。因此在某些情况下非常适用
定义多个属性Object.defineProperties(obj, props)
将defineProperty改为复数形式defineProperties,并将参数改为一个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {};
Object.defineProperties(obj, {
"property1": {
value: true,
writable: true
},
"property2": {
value: "Hello",
writable: false
}
// 等等.
});
console.log(obj); // Object {property1: true, property2: "Hello"}

读取给定属性的特性Object.getOwnPropertyDescriptor(obj, prop) 返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var man = {name: "gdc"};
console.log(Object.getOwnPropertyDescriptor(man,"name"));
// Object {value: "gdc", writable: true, enumerable: true, configurable: true}
Object.defineProperties(man,{
name:{
writable: false
},
age:{
writable: true,
value: 22
}
});
console.log(Object.getOwnPropertyDescriptor(man,"name"));
// Object {value: "gdc", writable: false, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(man,"age"));
// Object {value: 22, writable: true, enumerable: false, configurable: false}
var descriptor = Object.getOwnPropertyDescriptor(man,"age");
console.log(descriptor.value); // 22
console.log(descriptor.configurable); // false
console.log(descriptor.writable); // true
console.log(descriptor.get); // undefined
console.log(descriptor.set); // undefined

读取当前属性,返回获取数据形式

创建对象

1、工厂模式:

为了解决多个类似对象声明的问题,我们可以使用一种叫做 工厂模式的方法,这种方法就是为了解决实例化对象产生大量重复的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createObject(name,age,profession){//集中实例化的函数
var obj = new Object();
obj.name = name;
obj.age = age;
obj.profession = profession;
obj.move = function () {
return this.name + ' at ' + this.age + ' engaged in ' + this.profession;
};
return obj;
}
var test1 = createObject('trigkit4',22,'programmer');//第一个实例
var test2 = createObject('mike',25,'engineer');//第二个实例
alert(test1.move());
alert(test2.move());

利:
主要好处就是可以消除对象间的耦合,通过使用工程方法而不是new关键字。将所有实例化的代码集中在一个位置防止代码重复

弊:
大多数类最好使用new关键字和构造函数,可以让代码更加简单易读。而不必去查看工厂方法来知道。

何时使用?
1、当对象或组件涉及高复杂性时
2、当需要根据所在的不同环境轻松生成对象的不同实例时
3、当处理很多共享相同属性的小型对象或组件时

Constructor(构造器)模式

ECMAScript 中可以采用构造函数(构造方法)可用来创建特定的对象。 该模式正好可以解决以上的工厂模式无法识别对象实例的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Car(model,year,miles){//构造函数模式
this.model = model;
this.year = year;
this.miles = miles;
this.run = function () {
return this.model + " has done " + this.miles + "miles";
}
}
var Benz = new Car('Benz',2014,20000);
var BMW = new Car("BMW",2013,12000);
alert(Benz instanceof Car); //很清晰的识别他从属于 Car,true
console.log(Benz.run());
console.log(BMW.run());

和工厂模式相比:
1.构造函数方法没有显示的创建对象 (new Object());
2.直接将属性和方法赋值给 this 对象;
3.没有 renturn 语句。

原型模式
1
2
3
4
5
6
7
8
9
10
11
function Person(){}
Person.prototype.name = "gdccwxx";
Person.prototype.age =23;
Person.prototype.sayName = function(){
console.log(this.name)
};
var person1 = new Person();
person1.sayName() // gdccwxx
var person2 = new Person();
person2.sayName() // gdccwxx
person1.sayName == person2.sayName // true

Person类与构造函数,原型存在如下关系

Person的原型是person1和person2的原型。而Person.prototype.constructor右指回了Person
使用isPrototypeOf()判断对详见是否存在这种关系

1
2
Person.prototype.isPrototypeOf(person1) // true
Person.prototype.isPrototypeOf(person2) // true

虽然对象实例访问保存在原型中的值,却不能通过对象实例重写原型中的值。如果在对象里面添加一个属性,该属性只会存在于对象实例中,而屏蔽原型。即

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(){}
Person.prototype.name = "gdccwxx";
Person.prototype.age =23;
Person.prototype.sayName = function(){
console.log(this.name)
};
var person1 = new Person();
person1.sayName() // gdccwxx
var person2 = new Person();
person2.sayName() // gdccwxx
person1.name = 'guo'
person1.name // guo
person2.name // gdccwxx
delete person1.name
person1.name // gdccwxx

对象搜索属性中,先在实例中搜索,实例中有,则直接返回,实例中没有,则到其原型中查找。若在原型中未找到,则返回undefined;若找到则返回。因此,对person1实例添加name属性后,就屏蔽了原型里面的name,删除实例后的属性之后,又去原型里查找。
ps:原型属性不可被delete掉
hasOwnProperty()确定属性是否在实例上的方法
原型与in操作符
in有两种操作,第一种是在for-in循环中使用,另一种是会在通过对象能够访问的给定属性时返回true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Person(){}
Person.prototype.name = "gdccwxx";
Person.prototype.age =23;
Person.prototype.sayName = function(){
console.log(this.name)
};
var person1 = new Person();
var person2 = new Person();
person1.hasOwnProperty('name') // 在原型上,返回false
'name' in person1 //能够访问,true
person1.name = 'guo'
person1.name // guo
person1.hasOwnProperty('name') // true
'name' in person1 //能够访问,true
person2.name // guo
person2.hasOwnProperty('name') //false
'name' in person2 // true
delete person1.name
person1.name // gdccwxx
person1.hasOwnProperty('name') // 在原型上,返回false
'name' in person1 //能够访问,true

Object.keys()方法,获取对象所有可枚举实例属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function Person(){}
Person.prototype.name = "gdccwxx";
Person.prototype.age =23;
Person.prototype.sayName = function(){
console.log(this.name)
};
var keys = Object.keys(Person.prototype)
keys // name,age,sayName
var p1 = new Person();
p1.name = 'guo'
p1.age = '20'
var p1keys = Object.keys(p1)
p1keys // 'name,age'
// 获得所有实例属性,无论是否可枚举,使用Object.getOwnPropertyNames()
var keys = Object.getOwnPropertyNames(Person.prototype)
keys // ["constructor", "name", "age", "sayName"]
// 也可以使用另一种原型定义方法,字面量方法
function Person(){}
Person.prototype = {
name : 'gdccwxx',
age : 20,
sayName: function(){
//
}
}
//如果constructor很重要,也可以特意设定
Person.prototype = {
constructor: Person,
name : 'gdccwxx',
age : 20,
sayName: function(){
//
}
}
//这种重设constructor会导致其Enumerable属性变为true。如果要重设,应使用defineProperty
Object.defineProperty(Person.prototype, 'constructor',{
enumerable: false,
value: Person
})

由于是person指针指向Person的prototype,因此在Person任意改变prototype的情况下,会导致所有子元素都改变。因此大多都很少单独使用原型模式
组合使用构造函数模式和原型模式
将函数使用prototype模式,其他基本类型使用构造函数.可以达到节省内存,又拥有实例和副本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friend = ['a','b']
}
Person.prototype = {
constructor: Person,
sayName : function(){
console.log(this.name)
}
}
var person1 = new Person('gdccwxx', 20 ,'SoftWare')
var person2 = new Person('guo', 10 ,'Doctor')
person1.friends.push('van')
person1.friend // a,b,van
person2.friend // a,b
person1.friend === person2.friend //false
person1.sayName === person2.sayName //true

继承

原型链

原型链是JavaScript的主要实现继承方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function SuperType(){
this.property = true
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false
}
Subtype.prototype = new SuperType()
SubType.prototype.getSubValue = function(){
return this.subproperty;
}
var instance = new SubType();
var instance = new SubType()
instance.getSuperValue() //true

instance的原型连集成了SuperType方法

寄生组合式继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype) // 创建对象
prototype.constructor = subType // 增强对象
subType.prototype = prototype // 指定对象
}
function SuperType(name){
this.name = name;
this.colors = ['red','blue','green']
}
SuperType.prototype.sayName = function(){
return this.name
}
function SubType(name, age){
SuperType.call(this, name)
this.age = age
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
return this.age
}

通过上述继承方式,少用了一次构造函数,并且因此避免了在SuperType.prototype上的不必要创建、多余的属性

小结

通过本章的学习,深入理解了原型以及原型链上继承的问题。之前没用用过的defineProperty和prototype,都逐渐明白了其用处以及用法。通过原型链,构造函数等学习,让我对JavaScript的理解更上一步。以及对对象的理解更加深刻。不仅仅是属性以及value那么简单。对内存的分配以及效率的使用更加深刻。

JavaScript高级程序设计重读---5章

发表于 2017-07-12

引用类型

Object类型

1、JavaScript会将对象的属性自动转换成字符串

1
2
3
4
5
6
7
8
9
10
11
var person = {
"name":'gdccwxx',
"age": 29,
5:true
}
全等于
var person = {
"name":'gdccwxx',
"age": 29,
"5": true
}

ps:最后一个属性后面是不添加逗号的,如果添加,在IE7即更早之前版本和Opera出现错误

2、两种访问对象的方法。

(1)在对象后面使用.来访问对象的属性
(2)使用[‘’]来访问对象的属性

1
2
3
4
5
6
var person = {
"name":'gdccwxx',
"age": 29
}
person.name // gdccwxx
person['name'] // gdccwxx

第二种方法有好处,在对象的属性有空格的时候,只能用第二种

Array类型

数组是一组按序排列的值,相对地,对象的属性名称是无序的。从本质上讲,数组是按次序排列的一组值,数组是一种特殊的对象。

1
2
var arr = ['a', 'b', 'c'];
Object.keys(arr);// ["0", "1", "2"]

JavaScript的数据类型分为:值类型和引用类型(地址值);而常见的引用类型有Object和Array/数组的存储模型中,如果是诸如Number,String之类的类型数据会被直接压入栈中,而引用类型只会压入对该值的一个索引(即C中所说的保存了数据的指针)。这些数据时储存在堆中的某块区间中,堆栈并不是独立的,栈中也可以在堆中存放。

数组的创建

var arrayObj = new Array(); //创建一个数组
var arrayObj = new Array(size); //创建一个数组并指定长度,注意不是上限,是长度
var arrayObj = new Array([element0[, element1[, …[, elementN]]]]); //创建一个数组并赋值
var arrayObj = []

数组的检测

value instanceof Array // true or false
Array.isArray(value) // true or false

转换方法

调用Array.prototype.toString()方法,将Array转换成字符串

数组的几种操作方法
方法解释
shift获取数组的第一项并返回,减少数组的length值
unshift在数组前端添加任意个项并返回新数组的长度
pop从数组某位移除最后一项,减少数组的length值
push接受任意个参数,把他们逐个添加到数组末尾,病返回修改后数组的长度
reverse不接受参数,将数组项的顺序翻转
sort调用每个数组项的toString()转型方法,然后比较的到字符串,已确定如何重排,其返回值和reverse都是排序后的数组
concat创建一个数组副本,将参数添加到副本之后。接受一个或多个参数,返回连接后的数组
slice接受一个或两个参数,即要返回项的起始位置和结束位置,只有一个参数返回从指定位置到末尾项所有元项
splice(1)删除:指定两个参数,要删除的第一项的位置和要删除的项数(2)插入:指定三个参数,起始位置,0(要删除的项数)和要插入的个项。如果要插入多项,则传入多个参数(3)替换:指定三个参数:起始位置、要删除的项数和要插入的任一项。插入和删除的项数不必相等
indexOf从数组的开头位置(0)开始向后查找,没找到返回-1
lastIndexOf从数组的最后位置(length-1)开始向前查找,没找到返回-1
every对数组的每一项都运行给定函数,如果该数组的每一项都返回true,则返回true
filiter对数组的每一项运行给定函数,返回该函数会返回true的项组成的数组
forEach对数组的每一项运行给定的函数,这个方法没有返回值
map对数组的每一项运行给定函数,返回每次函数调用的结果组成的数组
some对数组中的每一项运行给定函数,任意一项返回true,则返回true
reduce从数组的第一项开始,逐个遍历到最后
reduceRight从数组的最后一项开始,逐个遍历到第一个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
var color = ['red','green']
// shift
var item1 = color.shift()
item1 // 'red' color = ['green'] color.length = 1
// unshift
var item2 = color.unshift('red','blue')
item2 // 3 color = ['red','blue','green']
// pop
var item3 = color.pop()
item3 // green color=['red','blue'] color.length = 2
// push
var item4 = color.push('gray')
item4 // 3 color = ['red','blue','gray'] color.length = 3
// reverse
color.reverse() //color = ['gray','blue','red']
// sort
function compare(value1,value2){
if(value1 < value2){
return 1;
}else if(value1 > value2){
return -1
}else {
return 0
}
}
color.sort(compare) //["red", "gray", "blue"]
// concat
var color2 = color.concat('red')
color2 //["red", "gray", "blue", "red"]
// slice
var removed1 = color.slice(1)
var removed2 = color.slice(1,3)
removed1 // "gray,blue,red"
removed2 // "gray,blue"
// splice
var removed3 = color.splice(0,1) // 删除第一项
removed3 // 'red'
var removed4 = color.splice(1,0,'origin','pink') // 从位置1插入两项
removed4 // 'red,origin,pink,gray,blue,red'
var removed5 = color.splice(1,1,'purple') // 插入一项,删除一项
removed5 // 'red,purple,blue,red'
// indexOf
var item10 = color.indexOf('blue')
item10 // 2
// lastIndexOf
var item11 = color.indexOf('red')
item11 // 3
// every
var item12 = color.every(function(item, index, array)){ //参数为item,迭代到的元素
return item == 'red' // index,索引,array,数组本身
}
item12 // false
// filiter
var item13 = color.filiter(function(item,index,array)){
return (item == 'red')
}
item13 // red,red
// forEach
color.forEach(function(item,index,array)){
//执行某些操作
}
// map
var item15 = color.map(function(item,index,array)){
return ('color is ' +item)
}
item15 // ["color is red", "color is gray", "color is blue", "color is red"]
// some
var item16 = color.some(function(item, index, array)){ //参数为item,迭代到的元素
return item == 'red' // index,索引,array,数组本身
}
item16 // true
// reduce()
var item17 = color.reduce(function(prev,cur,index,array)){
return (prev + ' + ' cur)
}
item17 // "red + gray + blue + red"
var item18 = color.reduceRight(function(prev,cur,index,array)){
return (prev + ' + ' cur)
}
item18 // "red + blue + gray + red"

Date类型

从国际时间1970年1月1日零时开始,可以精确到之后的100 000 000年

toString,toLocaleString
1
2
3
4
5
6
7
var date = new Date()
date
Wed Jul 12 2017 15:39:22 GMT+0800 (CST)
date.toLocaleString()
"7/12/2017, 3:39:22 PM"
date.toString()
"Wed Jul 12 2017 15:39:22 GMT+0800 (CST)"

如果是toString(),会直接返回标准的格式;
如果是toLocaleString(),先判断是否指定语言环境(locale),指定的话则返回当前语言环境下的格式设置(options)的格式化字符串;没有指定语言环境(locale),则返回一个使用默认语言环境和格式设置(options)的格式化字符串。

日期格式的方法
方法解释
toDateString特定显示的格式显示星期几,月,日,年
toTimeString显示时,分,秒
toLocalDateString显示地区的星期几,月,日,年
toLocalTimeString显示地区的时,分,秒
toUTCString显示完整格式的UTC日期
toLocaleString区别如上
toString区别如上
1
2
3
4
5
6
7
8
9
10
11
var date = new Date()
date.toDateString()
"Wed Jul 12 2017"
date.toTimeString()
"15:49:48 GMT+0800 (CST)"
date.toLocaleDateString()
"7/12/2017"
date.toLocaleTimeString()
"3:49:48 PM"
date.toUTCString()
"Wed, 12 Jul 2017 07:49:48 GMT"

RegExp类型

实例属性
方法解释
global布尔值,表示是否设置了g
ignoreCase布尔值,表示是否设置了i
lastIndex整数,表示开始搜索下一个匹配的字符标志,从0算起
multiline布尔值,表示是否设置了m标志
sorce正则表达式的字符串表示
实例方法

exec —接受一个参数,即要应用模式的字符串,然后返回包含一个匹配信息的数组,或者没有匹配返回null
test —接受一个字符串参数,在该模式下匹配成功返回true,否则返回false(只要存在即返回true)

1
2
3
4
5
6
7
8
var text = 'cat, bat, sat, fat'
var pattern = /.at/;
var matches = pattern.exec(text)
matches[0] //cat
matches.index // 0
var text = "456000-00-000123"
var pattern = /\d{3}-\d{2}-\d{2}/
pattern.test(text) // true

Function类型

每个函数都是Function类型的实例,而且都有与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数的指针,不会与某个函数绑定。
既然函数名是一个指针,所以JavaScript没有重载。
ps:访问函数指针时,应该不加圆括号

函数表达式和函数声明

由于JavaScript中有函数声明解析器,在所有函数执行前,会将函数声明提升至顶端。因此,函数表达式和函数声明会有一些区别

1
2
3
4
5
6
7
8
sum1(10,11)     //21
function sum1(num1,num2){
return num1+num2
}
sum2(10,12) //Uncaught TypeError: sum2 is not a function
var sum2 = function (num1,num2){
return num1+num2
}

其他无明显差别

callee caller

callee被调用者
caller调用者
mdn不建议使用

函数的属性和方法
1
2
3
4
5
6
7
8
function F1(a,b){
//
}
function F2(){
//
}
F1.length // 2
F2.length // 0

函数的length属性代表参数的个数

call与apply
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function sum(num1,num2){
return num1+num2
}
function callSum(num1, num2){
return sum.call(this,num1,num2)
}
callSum(10,10) //20
function sum(num1,num2){
return num1+num2
}
function callSum(num1, num2){
return sum.call(this,arguments)
}
callSum(10,10) //20

call和apply是两个非继承而来的方法,apply接受两个参数,一个是运行时函数的作用域,另一个是arguments对象或array实例;而call方法第一个参数是运行时函数作用域,其他参数是传入字面量。必须逐个列举出来

1
2
3
4
5
6
7
8
9
10
window.color = 'red'
var o = {
color:'green'
}
function sayCOlor(){
console.log(this.color)
}
sayCOlor.call(this) //red
sayCOlor.call(window) //red
sayCOlor.call(o) //green

call,apply的最大好处是让其扩充作用域,且实现松耦合

bind是创建一个实例,其this值会被绑定到传给bind函数的值

1
2
3
4
5
6
7
8
9
window.color = 'red'
var o = {
color : 'blue'
}
function sayColor(){
console.log(this.color)
}
var objectSayColor = sayColor.bind(o)
objectSayColor() //blue

基本包装类型

Boolean类型
1
2
3
4
5
6
7
8
9
10
var falseObject = new Boolean(false)
var result = falseObject && true
result // true
var falseValue = false
result = falseValue && true
result // false
typeof falseObject // object
typeof falseValue // boolean
falseObject instanceof Boolean //true
falseValue instanceof Boolean //false

基本类型布尔值与Boolean对象有一定差别,书上建议永远不要使用Boolean对象

Number类型
1
2
3
4
var numberObject = new Number(10)
numberObject // Number {[[PrimitiveValue]]: 10}
numberObject.toString() // "10"
numberObject.valueOf() // 10

Number类型重写了valueOf(),toLocaleString(),toString(),重写后,valueOf返回基本类型值
基本类型的几个方法

1、toFixed // 有一个参数,代表保留几位小数
2、toExponential // 指数表示法,表示制定输出结果中小数位数
3、toPrecision // 接受一个参数,合理的调用toFixed和toExponential
4、toString // 接受一个参数,代表转换成几进制

1
2
3
4
5
6
7
8
9
var num = 10
num.toString() //10
num.toString(1) //1010
num.toFixed(2) //10.00
num.toExponential(1) //1.0e+1
var nums = 99
num.toPrecision(1) //1e+2
num.toPrecision(2) // 99
num.toPrecision(3) // 99.0

String类型

1、字符方法
charAt —以单个字符串的形式返回给定位置的字符
charCodeAt —以单个字符串的形式返回给定位置的字符编码

1
2
3
var stringValue = 'hello world'
stringValue.charAt(1) // e
stringValue.charCodeAt(1) // 101

2、字符串方法
slice —接受两个参数,第一个是指定位置,第二个位结束位置,为负数从后往前切取
substr —接受两个参数,第一个是指定位置,第二个是结束位置,为负数从0往后开始
substring —接受两个参数,第一个是指定位置,第二个是字符个数,为负数从后往前切取
3、字符串位置
indexOf —接受两个参数,第一个是寻找字符,第二个是指定位置,默认从0开始往后
lastIndexOf —接受两个参数,第一个是寻找字符,第二个是指定位置,默认从后开始往前
4、trim
将字符串前后空格去除
5、大小写转换方法
toLowerCase //转换为小写
toUpperCase //转换为大写
toLocaleLowerCase
toLocaleUpperCase
6、replace
采用两个参数,第一个是匹配的正则表达式,第二个是替换内容
7、localeCompare方法
比较两个字符串,返回1,0,-1
8、fromCharCode方法
接受多个参数,将ASCII码转换成对应的字符

单体内置对象

1、Global对象
URI编码方法
(1)encodeURI,将空格转换成%20
(2)encodeURIComponent,将所有非字符转换成对应编码
(3)decodeURI,将encodeURI转换的uri反编码
(4)decodeURIComponent,将encodeURIComponent转换的uri反编码
2、eval方法
只接受一个参数,将字符串解析成JavaScript代码

1
eval('console.log(123)')  // 123

小结

通过本章学习,有许多细致的方法以前没有使用过的现在很多都理解了。还有数组的存储,以前只是一个黑匣子对于我而言。现在能够认清它的本质,能对以后的代码优化有更好的帮助。

JavaScript高级程序设计重读---4章

发表于 2017-07-11

变量、作用域、和内存问题

引用类型

Javascript不允许直接访问内存中的位置,意思位不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。为此,引用类型的值是按引用访问的

1
2
3
4
var obj1 = new Object();
var obj2 = obj1;
obj1.name = 'gdccwxx'
obj2.name // gdccwxx

对象是存在堆中,JavaScript只能引用堆中的Object。如图
heap
而由于复制后的两个变量对象都指向堆内存中的一个Object,因此改变一个后,另一个也会随着改变

函数在对象的传递是按值传递
1
2
3
4
5
6
7
8
unction setName(obj){
obj.name = 'gdccwxx'
obj = new Object();
obj.name = 'guo'
}
var person = new Object();
setName(person)
person.name // gdccwxx

解释:由于传入的person给obj复制了一份引用。因此一开始的时候是obj和person引用的是同一个。而第二段代码中,由于obj改变了新的引用。在改变之前,使person的name发生改变。而之后引用新的Object,因此person.name依然是gdccwxx。在函数执行之后,obj立即被销毁。

检测类型

利用typeof检查五个基本类型,而对于对象的类型检测,则采用instanceof
result = variable instanceof constructor

1
2
person instanceof Object    //true
pattern instanceof RegExp //true

作用域

通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。

延长作用域

利用with,以及try-catch语句中可以延长作用域

没有块级作用域
1
2
3
4
5
6
7
8
if(true){
var color = 'blue'
}
color // blue
for(var i = 0;i<10;i++){
doSomething(i)
}
i // 10

从上面可以看出,在花括号中是不算作用域的。因此,在es5的下是没有块级作用域的

垃圾收集

JavaScript是具有自动手机垃圾的机制的

标记清除

当变量进入环境时(如在函数声明一个变量时)九江变量标记位’进入环境’。从逻辑上讲,永远不能释放进入环境变量所占用的内存。当变量离开环境时,则将其标记为’离开环境‘

引用计数

引用一次,引用次数+1,引用另外一个值,引用-1,当到达0时,将其清除。
Bug:循环引用时,导致无法清除。多个引用存在时,导致内存消耗而崩溃。

性能问题

IE6使用内存分配,就是256个变量,4096个对象(或数组)字面量和数组元素或者64KB字符串执行自动清理。由于频繁清理,导致严重性能问题
IE7的垃圾收集机制,初始值和IE6相等,如果垃圾手机里程回收的内存分配量低于15%,则变量、字面量和数组元素的临界值就会翻倍。如果历程回收了85%,则重回默认值

内存管理

位确保占用最少的内存可以让页面获得更好的性能,优化内存的最佳方式。就是位执行的代码只保存必要的数据。一旦数据不在游泳,最好通过其值设为null来释放其引用。这叫接触引用。

小结

通过本章的学习,让我更理解了js这门语言的垃圾清理以及变量问题。采用简单的思路可以解决更好的问题。今后的coding会采用更简单,更高效的使用方法去解决问题

JavaScript高级程序设计重读—1-3章

发表于 2017-07-11

基本概念

关于script标签

1
<script src="/javascripts/application.js" type="text/javascript" charset="utf-8" async defer></script>

script标签共有src,type,charset,async,defer几个属性。在只有script时依然可以作用。
1、async是可选的,意为立即下载脚本,但不妨碍页面中的其他操作。且其下载后文件的执行顺序不定。

1
2
<script src="1.js" async></script>
<script src="2.js" async></script>

其下载后1.js和2.js不定制性。没有一定顺序,可能是1先,也可能是2先执行。

2、charset是指定字符集,在不写的情况下,默认charset为‘utf-8’编码。
3、type,在不谢的情况下默认值位‘text/javascript’
4、src属性,src属性并不是非得引用xx.js,而是其只要正确引用返回MIME类型即可。(mime类型:MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准。
MIME 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据。)如果有src属性,则script标签中即使有代码也不会执行。

script标签位置

由于浏览器是由上到下解析内容。如果放在head变迁内,浏览器要等其下载完成后,再去渲染整个页面。因此会有一段时间延迟渲染,造成用户体验差,因此,为了避免这个问题,现代web应用一般是放在body元素中页面内容的后面。加上defer延迟后,将在浏览器解析完成整个页面,即解析完成< /html>之后再执行js代码。

ps:defer,async都是先下载文件。加载页面完成后执行,但是不同点是defer保证js文件顺序执行,而async则不保证。

标识符

规则:
1、第一个字符必须是字母、下划线,或美元符号
2、其他字符可以使字母,下划线,美元符号或数字
最佳采用驼峰式命名

变量

未初始化的变量都有一个默认的值,undefined,若未使用var,则其将直接变成全局变量。可以使用逗号分隔,多个变量赋值,即

1
2
3
var a = 0,
b = 1,
c = 2

数据类型

JavaScript共有5种基本数据类型和1种复杂数据类型。分别是:
Undefined,Null,Boolean,Number,String;Object

TYPEOF操作

typeof操作只能返回下列字符串
undefined – 值未定义
boolean – 布尔值
string — 字符串
number – 数值
object – 对象或null
function – 函数

Undefined类型

显示的对一个值赋值为undefined和不初始化某个变量,其都===undefined

1
2
3
4
var message
// var age 此变量未声明
typeof message // undefined
typeof age //undefined

ps:对未初始化的变量执行typeof操作符会返回undefined,而对未声明的变量执行统一配发操作符同样也会返回undefined值。

Null类型

Null类型是表示一个空对象指针,也正是typeof null 返回Object的原因了。因此在声明一个为初始化的对象时,应该赋值为Null,在判断语句if中,null代表false。因此常用其作为未初始对象的赋值

Boolean类型
数据类型转换为true的值转换位为false的值
Booleantruefalse
String任何非空字符串“”空字符串
Number任何非零数值(包括无穷大)0和NaN
Object任何对象null
Undefined不存在的undefined
Number类型

1、如果前缀为 0,则 JavaScript 会把数值常量解释为八进制数,如果前缀为 0 和 “x”,则解释为十六进制数。即

1
2
var octalNum = 070 // 56
var hexNum1 = 0xA //10

在进行算术运算时其右会转化成10进制
2、JavaScript中+0和-0是相等的
3、由于浮点数的数值计算产生误差,即0.1 + 0.2 = 0.30000000000000004是因为给予IEEE754数值的浮点数计算的通病(详见:计算机系统基础--第二章(数据的机器级表示与处理) – 浮点数的表示)
4、由于浮点数的表示范围有限,因此其表示范围有Number.MIN_VALUE(最小值)和Number.MAX_VALUE(最大值)表示。采用isFinite()函数判断其是否溢出
5、NaN表示not a number 是一个特殊的值,表示其转换不是一个数值。任何数值的算术运算对NaN的结果都是NaN,且NaN不等于其本身。判断是否是NaN有专门函数isNaN()来判断。传入参数会先尝试转换成number再判断。而测试对象时先调用对象的valueOf方法,在确定是否可以转换成数值,如果不能,再调用toString方法。在测试返回值。
6、数值转换函数Number(),规则如下

1
2
3
4
5
6
7
8
9
10
(1)boolean,true转换成1,false转换成0
(2)数值,简单传入和返回
(3)null,返回0
(4)undefined,返回NaN
(5)字符串: <1>转换为十进制数,并且将前面多余的0去除
<2>浮点数格式相同
<3>如果包含0x、0o,将其转换成10进制数
<4>空字符串转换为0
<5>其他格式外都转换成NaN
(6)对象,先调用valueOf()方法,然后依照返回值,如果是NaN,则调用对象toString方法,再根据前面返回字符串

7、parseInt(),parseFloat()函数
parseInt也是将传入参数转换成数值,与Number函数不同的地方是,parseInt函数会选取从首字符开始的数值,到非数值字符结束,即parseInt(‘123abc’)转换成123,而Number转换成NaN。
parseInt可以接受第二个参数,代表转换成的基于格式。即

1
parseInt('10',2)  // 2

基于格式格式转化成相应十进制数

String类型

1、字符字面量,例如\n(换行),\t(制表)等等。这些字面量在字符串的任意位置都会被当做字符来解析。例如

1
2
var a = '\t\t\t'
a.length // 3

而a打印出来是三个制表
2、字符串的特点。

1
2
var lang = 'Java'
lang = lang + 'Script'

开始时lang保存字符串Java,而第二行把lang的值重新定义为Java和script结合,这个操作首先创建一个能容纳10个字符的新字符串,然后在这个字符串中填入java 和script,最后一步是销毁原来的字符串java和字符串script。
3、字符串的转换
利用toString方法来将数值转化成string,而其参数是转化成不同进制

1
2
3
4
var num = 11
num.toString() // '11'
var nums = 10
nums.toString(2) // '1010'

在不是null和undefined的情况下,还可以调用String()函数其转换规则如下
1、如果值有toString方法,调用其不带参数的toString方法
2、如果值是null,返回’null’
3、如果是undefined,返回’undefined’

Object类型

可以使用 var o = new Object()或者使用var o = new Object来构造Object实例。每个Object实例都有下列属性和方法
1、constructor 保存着创建当前对象的函数
2、hasOwnProperty 用于检查给定的属性在当前实例中(而不是实例的原型中)
3、isPrototypeOf 用于检查传入的对象是否是当前对象的原型
4、propertyIsEnumerable 用于检查给定的属性是否能用for-in语句来枚举
5、toLocaleString 返回对象的字符串表示,该字符串与执行环境的地区对应
6、toString 返回对象的字符串表示
7、valueOf 返回对象的字符串、数值或布尔值

位操作符

1、按位非 (NOT) ~

1
2
3
var num1 = 25   // 二进制00000000000000000000000000011001
var num2 = ~num1// 二进制11111111111111111111111111100110
num2 // ~26

2、按位与 (AND) &

1
2
3
4
5
6
var result = 25 & 3
result // 1
25 = 0000 0000 0000 0000 0000 0000 0001 1001
3 = 0000 0000 0000 0000 0000 0000 0000 0011
----------------------------------------------
AND= 0000 0000 0000 0000 0000 0000 0000 0001

3、按位或 (OR) |

1
2
3
4
5
6
var result = 25 | 3
result // 27
25 = 0000 0000 0000 0000 0000 0000 0001 1001
3 = 0000 0000 0000 0000 0000 0000 0000 0011
----------------------------------------------
AND= 0000 0000 0000 0000 0000 0000 0001 1011

4、按位异或 (XOR) ^

1
2
3
4
5
6
var result = 25 ^ 3
result // 27
25 = 0000 0000 0000 0000 0000 0000 0001 1001
3 = 0000 0000 0000 0000 0000 0000 0000 0011
----------------------------------------------
AND= 0000 0000 0000 0000 0000 0000 0001 1010

5、 操作符

1
25**2 = 625

**代表次方

语句

label语句结合break,continue语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var num = 0;
outermost:
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
if (i == 5 && j == 5) {
break outermost
}
}
}
num // 55
var num = 0;
outermost:
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
if (i == 5 && j == 5) {
continue outermost
}
}
}
num // 95

lable结合break,continue语句可以控制跳出位置

with语句

将代码作用域设定到特定对象中。

1
2
3
4
5
6
7
8
9
var qs = location.search.substring(1)
var hostName = location.hostname
var url = location.href
//使用with语句后
with(location){
var qs = search.substring(1)
var hostName = hostname
var url = href
}

小结

通过本章学习,发现许多细节问题之前没有搞清楚,一直使用老方法去引用script。变量命名一直也是随意命名,今后的学习、coding之路,让我更清晰,明了基础的知识

计算机系统基础--第五章(程序的执行)

发表于 2017-05-25

按照ISA的复杂程度而言,有复杂指令集(CISC)和精简指令集(RISC)两种类型的计算机。而intel就是属于前者

程序执行的概述

指令周期:CPU取出并执行一条指令的时间成为指令周期,不同指令的周期时间可能不同。
执行程序顺序:

1
2
3
4
5
6
1、取指令:从pc所支出的存储单元取出指令送到指令寄存器
2、指令译码:例如,mov $0x0,%eax,将立即数送到寄存器ebp中
3、计算源操作数地址并取操作数:根据寻址方式确定源操作数地址计算方式,若是存储器数据,则最多一次
4、执行数据操作:在ALU或加法器等运算部件中取出的操作数进行计算
5、计算目的操作数地址并存结果:根据寻址方式确定目的操作数地址计算方式,若是存储器数据,则最多一次,若是寄存器数据,则在进行数据操作时直接存结果到寄存器
6、计算下一条指令地址:顺序执行时,将pc加上当前指令长度即可。如果是转移类指令时,需要根据条件标识、操作码和寻址方式确定下一条指令

基本操作:

1
2
3
4
1、读取某储存单元内容(可能是指令或操作数或操作数地址),并将其装入某个寄存器。
2、把一个数据从某个寄存器存储到给定存储单元。
3、把一个数据从某个寄存器传送到另一个寄存器或ALU。
4、在ALU中进行某种算术运算或逻辑运算,并将结果送到某个寄存器中。

cup的基本功能和组成

由程序计数器(PC),指令寄存器(IR),指令译码器(ID),脉冲源及起停控制逻辑,时序信号产生部件,操作控制信号形成部件,总线控制逻辑,终端机构组成

打断程序正常执行的事件

1、内部异常:源自于正在运行程序本身,来自于cpu内部。是必然,同步事件。
2、外部中断:源自于正在运行程序的条件,来足浴cpu外部,是偶然,异步事件。

数据通路基本逻辑和工作原理

操作元件:多路选择器(MUX),加法器(ADDER),算术逻辑部件(ALU),译码器(Decoder)
状态元件:D触发器,这需要建立时间以及保持时间,也称触发器的锁存延迟。
数据通路的时钟周期:cycle Time = clk-to-Q(稳定时间)+Longest Delay(处理时间)+ 建立时间+时钟偏移。
工作原理:
simpleBus
数据通路中的部件之间可以通过总线方式连接,ALU及所有的寄存器通过一条内部的公共总线连接起来,已构成单总线结构数据通路,因为在cpu内部成为cpu内总线。
1、完成算术或逻辑运算

1
2
3
4
其完成R[R3]<-R[R1]+R[R2]操作如下:
1、R1(out),Y(in)
2、R2(out),add,Z(in)
3、Z(out),R3(in)

2、从内存读取一个字(指令或数据或数据的地址)

1
2
3
4
其完成R[R2]<-M[R[R1]]操作如下:
1、R1(out),MAR(in)
2、read,WMFC(使cpu进行等待)
3、MAR(out),R2(in)

3、把一个字(数据)写入内存

1
2
3
4
其完成M[R[R2]]<-R[R1]操作如下:
1、R1(out),MDR(in)
2、R2(out),MAR(in)
3、write,WMFC

流水线方式下命令的执行

想象以下,你有一堆衣服,有洗,甩干,晾三个操作,分别时间是40,30,20。有洗衣机和甩干机,晾必须得自己操作,当然,洗衣间衣服的时间是40+30+20,若洗n件衣服,依然还是使用这种算法,则总时间为n(40+30+20),而我们最好的方法是:

在洗第n件衣服时,正好在甩干第n-1件,在晾第n-2件。而大家都同时以40分钟时间为标准,因此。最后的总时间为:40n+30+20,节省不少时间。
在计算机中也是如此,设计原则是:
指令流水段个数以最复杂指令所用的功能段数为准,流水段的长度以最复杂的功能段的操作所用的时间为准。
因此,比较适合流水线的指令集特征为:
1、指令长度尽量一致。
2、指令格式尽量规整,尽量保证源寄存器的位置相同
3、采用load/store型指令风格(指令集中只有load指令和store指令能访问内存储器,其他一律不行)
4、数据和指令要在寄存器中“对齐存放”

流水线方式下出现问题

1、结构冒险:可能逻辑A与逻辑D存放的正好是同一个寄存器,而同时需要运行这条指令,造成结构冒险,由于硬件共享产生的问题。
解决方案:
(1)、规定一个部件的每条指令只能使用一次,而且只能特定阶段使用
(2)、通过设置多个独立的部件来避免资源的冲突

2、数据冒险:后面的指令用到前面指令的运算结果,前面的指令还没有完成,结果还没有算出,造成数据冒险。
解决方案:
(1)、由编译器在数据相关的指令之间加若干个nop(空操作指令)指令,等待上一条数据的产生
(2)、采用数据转发机制,即结果出来后通过旁路放到相关的exce阶段的ALU输入端
(3)、通过硬件阻塞(插入气泡)来延迟load命令执行
(4)、通过寄存器的读写操作进行特殊处理,保证在一个时钟的前半周期进行寄存器写,在后半期进行寄存器读。

3、控制冒险:
由于中断或转移指令的执行顺序,可能发生控制冒险。
可通过插入气泡或插入空操作符解决。

小结:

计算机程序的运行并没有那么简单,从简单的代码,转化为汇编之后,再变成单条的运行,之间发生了奇妙的变化,这章的学习让我明白软件的运行并不是简单我们看到的代码,而是分解之后单条单条的汇编代码,每条汇编代码又需要执行条小周期,才能完成一条汇编代码。而每条小周期还能变成一个完整的流水线。即使每个流水线都是有各种各样的问题,但解决流水线问题之后,速度是质的提升。这种思想也改变了我的思维,对代码效率的考虑。

计算机系统基础--第四章(程序的链接)

发表于 2017-05-24

什么是程序的链接?

将gcc生成的若干个.0文件为后缀的,vc输出为.obj为后缀的可重定位目标文件组合起来,生成一个可执行目标文件

程序链接的好处:

1、模块化(可分成不同模块全部编译成.o文件后链接即可运行。而不仅仅是只有一个.c文件,对于大工程来说清晰划分)
2、效率高(由于可以使多个文件同时编写,多个模块同时修改,因此这样提升了效率)

在链接先进行编译和汇编,在第三章聊过

可执行文件与.o文件的汇编差别在哪?

由于可执行文件是将可重定位二进制文件重新组合,因此其自身的虚拟地址转化为了逻辑地址,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// test.c
int main(int i,int j){
int x = i+j;
return x;
}
//使用objdump -d test.o反汇编出来的.o文件
// test.o
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 89 7d ec mov %edi,-0x14(%rbp)
7: 89 75 e8 mov %esi,-0x18(%rbp)
a: 8b 55 ec mov -0x14(%rbp),%edx
d: 8b 45 e8 mov -0x18(%rbp),%eax
10: 01 d0 add %edx,%eax
12: 89 45 fc mov %eax,-0x4(%rbp)
15: 8b 45 fc mov -0x4(%rbp),%eax
18: 5d pop %rbp
19: c3 retq
//每个.o文件开头都是以0开始,还没有被链接成位可执行文件,在其内部为虚拟地址
//使用objdump -d test反汇编出来的可执行文件
//test
00000000004004d6 <main>:
4004d6: 55 push %rbp
4004d7: 48 89 e5 mov %rsp,%rbp
4004da: 89 7d ec mov %edi,-0x14(%rbp)
4004dd: 89 75 e8 mov %esi,-0x18(%rbp)
4004e0: 8b 55 ec mov -0x14(%rbp),%edx
4004e3: 8b 45 e8 mov -0x18(%rbp),%eax
4004e6: 01 d0 add %edx,%eax
4004e8: 89 45 fc mov %eax,-0x4(%rbp)
4004eb: 8b 45 fc mov -0x4(%rbp),%eax
4004ee: 5d pop %rbp
4004ef: c3 retq
// 由于我的机子是8g内存,因此开头以400...开头
// test.o与test相比,test经过链接后,将虚拟地址转化成为逻辑地址

目标文件格式

ELF格式
每个可执行文件都有一个ELF头,里面包括着可执行文件的信息。

ELF头

elfImage
夹在ELF头和节头部表之间的都是节。一个典型的ELF可重定位目标文件包含下面几个节:
.text:已编译程序的机器代码。
.rodata:只读数据,比如printf语句中的格式串和开关(switch)语句的跳转表。
.data:已初始化的全局C变量。局部C变量在运行时被保存在栈中,既不出现在.data中,也不出现在.bss节中。
.bss:未初始化的全局C变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。目标文件格式区分初始化和未初始化变量是为了空间效率在:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。
.symtab:一个符号表(symbol table),它存放在程序中被定义和引用的函数和全局变量的信息。一些程序员错误地认为必须通过-g选项来编译一个程序,得到符号表信息。实际上,每个可重定位目标文件在.symtab中都有一张符号表。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的表目。
.rel.text:当链接噐把这个目标文件和其他文件结合时,.text节中的许多位置都需要修改。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面调用本地函数的指令则不需要修改。注意,可执行目标文件中并不需要重定位信息,因此通常省略,除非使用者显式地指示链接器包含这些信息。
.rel.data:被模块定义或引用的任何全局变量的信息。一般而言,任何已初始化全局变量的初始值是全局变量或者外部定义函数的地址都需要被修改。
.debug:一个调试符号表,其有些表目是程序中定义的局部变量和类型定义,有些表目是程序中定义和引用的全局变量,有些是原始的C源文件。只有以-g选项调用编译驱动程序时,才会得到这张表。
.line:原始C源程序中的行号和.text节中机器指令之间的映射。只有以-g选项调用编译驱动程序时,才会得到这张表。
.strtab:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表就是以null结尾的字符串序列。
旁注:为什么未初始化的数据称为.bss?
用术语.bss来表示未初始化的数据是很普遍的。它起始于IBM 704汇编语言(大约在1957年)中”块存储开始(Block Storage Start)“指令的首字母缩写,并沿用至今。一个记住区分.data和.bss节的简单方法是把“bss”看成是“更好地节省空间(Better Save Space)!“的缩写。

符号表和符号解析

符号表的类型

1、在模块m中定义冰杯其他模块引用的全局符号
2、在其他模块定义并且被m引用的外部符号
3、在模块m中定义并在m中引用的本地符号

使用readelf -s prog.o查看符号表
1
2
3
4
5
6
7
8
9
10
11
12
13
readelf -s main.o
Symbol table '.symtab' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
8: 0000000000000000 8 OBJECT GLOBAL DEFAULT 3 buf
9: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 main
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND swap
readelf -s swap.o
Symbol table '.symtab' contains 12 entries:
Num: Value Size Type Bind Vis Ndx Name
5: 0000000000000000 8 OBJECT LOCAL DEFAULT 5 bufp1
9: 0000000000000000 8 OBJECT GLOBAL DEFAULT 3 bufp0
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND buf
11: 0000000000000000 60 FUNC GLOBAL DEFAULT 1 swap

GLOBAL位全局变量,LOCAL为局部变量,swap为函数,Ndx位本地变量,UND为undefined

符号解析
全局符号的强弱特性

定义:函数名和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。
例如上面的,main,buf,swap,bufp0是 强符号,bufp1位本地符号,本地符号无强弱之分。

多重定义符号处理:
规则1:强符号不能多次定义,也即强符号只能被定义一次,否则链接错误
规则2:若一个符号被说明为一次强符号定义和多个弱符号定义,则按强符号为准
规则3:若有多个弱符号定义,则任选其中一个

强弱符号链接错误解决方案:

1、尽量避免使用全局符号
2、把全局符号定义位static,这样就没有强弱之分
3、尽量要给全局变量赋初值使其变成强符号
4、外部全局变量尽量使用extern

重定位

规则:
函数调用采用相对重定位
即使用R_386_PC32:ADDR(r_sym)-((ADDR(.text)+ r_offset) - init)
全局变量采用绝对重定位
即使用R_386_32把32位的地址值直接代替

重定位的工作

1、节和定义符号的重定位
2、引用符号的重定位

链接

1、静态链接:将用户程序中使用的库文件完整拷贝形成一个完整的可执行文件
优点:可随时执行文件,可执行文件不会因为库文件丢失而无法执行
缺点:导致相同库文件多个备份
2、动态链接:根据代码、数据、重定位和符号表信息,能在执行目标文件是装入或运行被动态的装入内存并自动链接
优点:减少库文件的多个备份
缺点:缺少库文件无法运行

小结:

至此,第四章也完成了。这一章主要讲的是生成可执行文件的过程,由最初始的编辑代码,然后将其预编译,编译,汇编,链接,生成可执行文件。这个过程是不仅由我们看到的小黑框那么简单,这段时间还经历着将代码转成汇编,将所定义的全局变量以及函数名称建表,把各个符号通过重定位形成一个最终没有缺失的可执行文件。第四章让我很清晰的清楚可执行文件的生成,也把当初的黑匣子打开,让我更深刻的理解其工作原理。

计算机系统基础--第三章(程序的转换及机器级表示)

发表于 2017-05-23

生成机器代码的过程

1、预处理。:例如,在C语言中有程序以#开头的语句,在源程序中插入所有用的#include命令指定的文件和用#define申明的宏

1
cc -E prog1.c -o prog1.i  //对prog1.c进行预处理,预处理结果位prog1.i

2、编译。将预处理后的源程序文件编译产生相应的汇编语言程序

1
gcc -S prog1.i -o prog1.s或gcc -S prog1.c -o prog1.s    //对prog1.i或者prog1.c进行编译,生成汇编代码文件prog1.s

3、汇编。由汇编程序将汇编语言程序文件转换位可重定位的机器语言目标代码文件

1
gcc -c prog1.s -o prog1.o //对prog1.s进行汇编,生成可重定位目标文件prog1.o

3、汇编。由汇编程序将汇编语言程序文件转换位可重定位的机器语言目标代码文件

1
gcc prog1.o prog2.o -o prog     //将两个可重定位目标文件prog1.o prog2.o链接起来,生成可执行文件prog

可以使用gdb来进行调试

在Linux中自带GNU调试工具gdb调试和跟踪。
在生成.o文件后使用objdump -d test.o来进行反汇编查看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// test.c
int add(int i,int j){
int x = i+j;
return x;
}
// test.o
0000000000000000 <add>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 89 7d ec mov %edi,-0x14(%rbp)
7: 89 75 e8 mov %esi,-0x18(%rbp)
a: 8b 55 ec mov -0x14(%rbp),%edx
d: 8b 45 e8 mov -0x18(%rbp),%eax
10: 01 d0 add %edx,%eax
12: 89 45 fc mov %eax,-0x4(%rbp)
15: 8b 45 fc mov -0x4(%rbp),%eax
18: 5d pop %rbp
19: c3 retq

MASM采用的是Intel格式的汇编代码

1
MOV [BX+DI-6],CL   //其对大小写不明感,且目的操作数在做,而源操作数在右

AT&T方式(教材使用方式)

1
mov %ecx,(%ebx,%edi,-6)  // R[ecx] <- R[ebx]+M[R[edi]-6]

寄存器组织和寻址方式

通用寄存器(General Pupose Regesters,32位,8个)

段寄存器(Segment Registers,16位,6个)

程序状态与控制寄存器(Program Status and Control Register,32位,1个)

指令指针寄存器(Instruction Pointer,32位,1个)

1.通用寄存器

EAX:累加器(Accumulator,针对操作数和结果数据的)

EBX:基址寄存器(Base,DS段中的数据指针)

ECX:计数器(Count,字符串和循环操作的)

EDX:数据寄存器(Data,I/O指针)

以上4个寄存器主要用在算术运算指令中,常常用来保存常量与变量的值。

EBP:扩展基质指针寄存器(Base Pointer,SS段中栈内数据指针)

ESI:源变址寄存器(Source Index,字符串操作源指针)

EDI:目的变址寄存器(Destination Index,字符串操作目标指针)

ESP:栈指针寄存器(Stack Pointer,SS段中栈指针)

以上4个寄存器主要用作保存内存地址的指针。

2.段寄存器

CS:代码段寄存器(Code Segment)

SS:栈段寄存器(Stack Segment)

DS:数据段寄存器(Data Segment)

ES:附加数据段寄存器(Extra Data Segment)

FS:数据段寄存器(Data Segment)

GS:数据段寄存器(Data Segment)

CS寄存器用于存放应用程序代码所在段的段基址,SS寄存器用于存放栈段的段基址,DS寄存器用于存放数据段的段基址。ES、FS、GS寄存器用来存放程序使用的附加数据段的段基址。

purposeRegiesters
sliceRegiester

3.程序状态与控制寄存器

EFLAGS:Flag Register,标志寄存器
标志寄存器
eflag

运算结果标志位

1、进位标志CF(Carry Flag)
进位标志CF主要用来反映运算是否产生进位或借位。如果运算结果的最高位产生了一个进位或借位,那么,其值为1,否则其值为0。
使用该标志位的情况有:多字(字节)数的加减运算,无符号数的大小比较运算,移位操作,字(字节)之间移位,专门改变CF值的指令等。
2、奇偶标志PF(Parity Flag)
奇偶标志PF用于反映运算结果中“1”的个数的奇偶性。如果“1”的个数为偶数,则PF的值为1,否则其值为0。
利用PF可进行奇偶校验检查,或产生奇偶校验位。在数据传送过程中,为了提供传送的可靠性,如果采用奇偶校验的方法,就可使用该标志位。
3、辅助进位标志AF(Auxiliary Carry Flag)
在发生下列情况时,辅助进位标志AF的值被置为1,否则其值为0:
(1)、在字操作时,发生低字节向高字节进位或借位时;
(2)、在字节操作时,发生低4位向高4位进位或借位时。
对以上6个运算结果标志位,在一般编程情况下,标志位CF、ZF、SF和OF的使用频率较高,而标志位PF和AF的使用频率较低。
4、零标志ZF(Zero Flag)
零标志ZF用来反映运算结果是否为0。如果运算结果为0,则其值为1,否则其值为0。在判断运算结果是否为0时,可使用此标志位。
5、符号标志SF(Sign Flag)
符号标志SF用来反映运算结果的符号位,它与运算结果的最高位相同。在微机系统中,有符号数采用补码表示法,所以,SF也就反映运算结果的正负号。运算结果为正数时,SF的值为0,否则其值为1。
6、溢出标志OF(Overflow Flag)
溢出标志OF用于反映有符号数加减运算所得结果是否溢出。如果运算结果超过当前运算位数所能表示的范围,则称为溢出,OF的值被置为1,否则,OF的值被清为0。
“溢出”和“进位”是两个不同含义的概念,不要混淆。

状态控制标志位

状态控制标志位是用来控制CPU操作的,它们要通过专门的指令才能使之发生改变。
1、追踪标志TF(Trap Flag)
当追踪标志TF被置为1时,CPU进入单步执行方式,即每执行一条指令,产生一个单步中断请求。这种方式主要用于程序的调试。
指令系统中没有专门的指令来改变标志位TF的值,但程序员可用其它办法来改变其值。
2、中断允许标志IF(Interrupt-enable Flag)
中断允许标志IF是用来决定CPU是否响应CPU外部的可屏蔽中断发出的中断请求。但不管该标志为何值,CPU都必须响应CPU外部的不可屏蔽中断所发出的中断请求,以及CPU内部产生的中断请求。具体规定如下:
(1)、当IF=1时,CPU可以响应CPU外部的可屏蔽中断发出的中断请求;
(2)、当IF=0时,CPU不响应CPU外部的可屏蔽中断发出的中断请求。
CPU的指令系统中也有专门的指令来改变标志位IF的值。
3、方向标志DF(Direction Flag)
方向标志DF用来决定在串操作指令执行时有关指针寄存器发生调整的方向。在微机的指令系统中,还提供了专门的指令来改变标志位DF的值。

32位标志寄存器增加的标志位

1、I/O特权标志IOPL(I/O Privilege Level)
I/O特权标志用两位二进制位来表示,也称为I/O特权级字段。该字段指定了要求执行I/O指令的特权级。如果当前的特权级别在数值上小于等于IOPL的值,那么,该I/O指令可执行,否则将发生一个保护异常。
2、嵌套任务标志NT(Nested Task)
嵌套任务标志NT用来控制中断返回指令IRET的执行。具体规定如下:
(1)、当NT=0,用堆栈中保存的值恢复EFLAGS、CS和EIP,执行常规的中断返回操作;
(2)、当NT=1,通过任务转换实现中断返回。
3、重启动标志RF(Restart Flag)
重启动标志RF用来控制是否接受调试故障。规定:RF=0时,表示“接受”调试故障,否则拒绝之。在成功执行完一条指令后,处理机把RF置为0,当接受到一个非调试故障时,处理机就把它置为1。
4、虚拟8086方式标志VM(Virtual 8086 Mode)
如果该标志的值为1,则表示处理机处于虚拟的8086方式下的工作状态,否则,处理机处于一般保护方式下的工作状态

4.指令指针寄存器

EIP:指令指针寄存器(Instruction Pointer),存放下次将要执行的指令在代码段的偏移量。

七种寻址方式

findWay
定义以下几个类型

1
2
3
int x;
float a[100];
short b[4][4];

假设x的基址位100,每个int元素占4bit,则
a[i] = 104+i4 //比例变址
b[i][j] = 504+i8+j*2 //基址+比例变址+位移
x = 100 // 基址

IA-32常用指令及其操作

传送指令

1、mov movb(比特), movw(字), movl(双字)
2、movs 符号扩展传送指令
3、movz 零扩展传送指令
4、xchg 数据交换指令
5、push 压栈
6、pop 退栈
7、lea 地址传送指令
8、in,out 输入输出I/O指令
9、pushf,popf 标志传送指令

扩展

符号位扩展:
八位扩展为十六位
由 00001000 -> 1111111100001000
零扩展:
八位扩展为十六位
由 00001000 -> 0000000000001000
假设val 和ptr声明如下

val_type val;
contofptr_type ptr;
已知上述类型val_type和contofptr_type是用typeof声明的数据类型,且val存储在累加器al/ax/eax中,ptr存储在edx中,现有以下两条C语言语句:
val= (val_type)
ptr;
*ptr = (contofptr_type) val;
写出以下组合类型的mov指令实现

val_typecontofptr_type
charchar
intchar
unsignedint
intunsigned char
unsignedunsigned char
unsigned shortint

答案:

val_typecontofptr_type语句一对应的操作语句二对应的操作
charcharmovb (%edx),%al //传送movb %al,(%edx)//传送
intcharmovsb (%edx),%eax // 符号位扩展,传送movb %al,(%edx) //截断,传送
unsignedintmovl (%edx),%eax //传送movl %eax,(%edx) //传送
intunsigned charmovzbl (%edx),%eax // 零扩展,传送movb %al,(%edx) //截断,传送
unsignedunsigned charmovzbl (%edx),%eax // 零扩展,传送movb %al,(%edx) //截断,传送
unsigned shortintmovw (%edx),%ax // 截断,传送movzwl %ax,(%edx) //零扩展,传送

其在寄存器中以小端方式储存
即|12345678H|-> |78H|56H|34H|12H|

按位运算指令

1、NOT单操作数每位取反
2、AND对双操作数按位逻辑“与”
3、OR对双操作数按位逻辑“或”
4、XOR对双操作数按位逻辑“异或”
5、TEST根据两个操作数相“与”的结果来设置条件标志
6、SHL逻辑左移,每左移一次,最高位送入cf,并在低位补0
7、SHR逻辑右移,每右移一次,最低位送入cf,并在高位补0
8、SAL算术左移,每左移一次,最高位送入cf,并在低位补0,若符号位发生变化,则of=1,表示左移溢出
9、SAR算术右移,每右移一次,最低位送入cf,并在高位补0
10、ROL循环左移,每左移一次,最高位移到最低位,并送入cf
11、ROR循环右移,每右移一次,最低位移到最高位,并送入cf
12、RCL带循环左移,将CF作为操作数的一部分循环左移
13、RCR带循环右移,将CF作为操作数的一部分循环右移

控制转移指令JMP
条件转移指令
根据单个标志位的状态判断转移的指令
指令转移条件说明
JC DESTCF=1有进位/借位
JNC DESTCF=0无进位/借位
JE/JZ DESTZF=1相等/等于零
JNE/JNZ DESTZF=0不相等/不等于零
JS DESTSF=1是负数
JNS DESTSF=0是正数
JO DESTOF=1有溢出
JNO DESTOF=0无溢出
JP/JPE DESTPF=1有偶数个“1”
JNP/JPO DESTPF=0有奇数个“1”
根据两个无符号数的比较结果判断转移的指令
指令转移条件含义
JG/JNLE DESTSF=OF AND ZF=0有符号数A>B
JGE/JNL DESTSF=OF OR ZF=1有符号数A≥B
JL/JNGE DESTSF≠OF AND ZF=0有符号数A<B
JLE/JNG DESTSF≠OF OR ZF=1有符号数A≤B
根据两个有符号数的比较结果判断转移的指令
指令转移条件含义
JG/JNLE DESTSF=OF AND ZF=0有符号数A>B
JGE/JNL DESTSF=OF OR ZF=1有符号数A≥B
JL/JNGE DESTSF≠OF AND ZF=0有符号数A<B
JLE/JNG DESTSF≠OF OR ZF=1有符号数A≤B

IA-32的栈、栈帧及其结构

static picture

注:为保证其在内存中的整齐,一般的存在为16的倍数。

小结:

本章至此也基本结束了,通过这章的学习,让我更清晰程序的内部工作原理,对优化程序也有了一定的见解。比如递归程序消耗内存,以及switch要查表才可以找到相应的选项。对汇编程序也有了一定的理解。虽然并不是那么深刻。但对以后思考代码的逻辑性以及效率性有了一定的帮助。

计算机系统基础--第二章(数据的机器级表示与处理)

发表于 2017-05-22

数制转换

即将不同的数值转换,例如:二、八、十六进制的相互转换

1
2
3
八进制转二进制: 0 Q = 000 B    1 Q = 001 B     7 Q = 111 B
十六进制转二进制: 0 H = 0000 B 5 H =0101 B F H = 1111 B
tips: 可知八进制转二进制与十六进制转二进制分别对应着3位以及4位的二进制

其他进制转二进制,例如:十进制与二进制的相互转换

1
2
0 D = 0000 H    10 D = 1010 B   100 D = 1100100 B
而无明显进制位数比较

多进制转化为10进制,例如16进制转化为10进制

1
2
1 H = 1*16^0 D      F H = 15*16^0 D     8FF H = 8*16^2+15*16^1+15*16^0 D
可较为明确的转化

结论:多进制转换为二进制无明显比较。可由多进制转化为10进制后,再转化为其他进制。可参简易的进制转换器

定点编码表示

原码表示法

也称“符号-数值”
规则:
1、当Xt 为正数时, Xn-1 = 0 Xi=X’i(0<=i<=n-2);
2、当Xt 为负数时, Xn-1 = 1 Xi=X’i(0<=i<=n-2);
即:-10 用八位原码表示为10001010B,10 用八位原码表示为00001010B
优点:真之对应关系直接方便简单,而且用原码实现乘除运算也较为简单。
缺点:0表示不唯一(0000,1000都表示0)加减运算需判断是否同好或异号。
运用:现代计算机中不用原码表示正数,只用定点源码小数表示浮点数的尾数部分。

补码表示法

也称“2-补码”
规则:
1、当Xt 为正数时, [Xt]补 = Xt = M+Xt(mod M);
2、当Xt 为负数时, [Xt]补 = M - |Xt| = M+Xt(mod M);
即:[1101100]补 = 2^8 + 1101100 = 100000000 + 1101100(mod 2^8) = 01101100
[-1101100]补 = 2^8 - 1101100 = 100000000 - 1101100 = 10000000 + (1111111 - 1101100) + 1 = 10000000 + 0010011 + 1(mod 2^8) = 10010100
优点:1、减少了-0与+0的切换
2、占用少一个编码表示,补码比原码能多表示一个最小负数,可以用-2^(n-1)来表示最小负数。
3、两数的补码之和(差)=两数和(差)的补码。
运用:在计算机中,补码用来表示带符号正数。

反码表示法

规则:
正数和原码相同,负数的补码采用“各位取反,末尾加1”
即: [01100]反 = [10100]反
缺点:1、0的表示不唯一。
2、表数范围比补码少一个最小负数。
3、运算时必须考虑循环进位。
运用:反码在计算机中很少被使用,有事做数码变换的中间表示形式(我们可以快速从反码知道原码和补码)。

移码表示法

规则:
[E]移 = 偏置常数 + E (偏置常数通常取2^(n-1)或2^(n-1)-1)
运用:通常表示浮点数的阶(即指数)

浮点数的表示(IEEE754浮点数)

IEEE754浮点数的表示:
ieee754image
chart_of_iee754
sign: 符号位,0表示正数1表示负数
exponent: 阶码,由偏置常数+偏移量组成
function: 位数

为什么要有IEEE754标准?

答:在此标准出来之前,不同电脑中的浮点数表示不唯一,一个程序由此电脑移到彼电脑可能需要重新编码。为了让程序员更加专心于程序的本身,而非多个不同电脑的差异做无用功,因此出现IEEE754标准

什么是IEEE754标准?

以双精度为例,相比于传统浮点位,IEEE754标准使传统阶码标准之上,偏置常数从原来的10000000000(2^(n-1))变为00000000000(2^(n-1)-1),即隐藏移码,从原来的1024变成1023,这种做法有两个好处
1、尾数可表示的位数多一位,因而使得浮点数的精度更高。
2、阶码的可表示范围更大,因而使浮点数表示范围更大

IEEE754解释:
question:

请判断下列关系表达式在32位机子上运行上是否永真,去除无穷大和NAN的任何值,i、f、d分别表示位,int,float,double

1
2
3
4
1、 i == (int)(float) i   //不相等,因为int为32位,float为23位精度
2、 f == (float)(int)f //不相等,小数位精度缺失
3、 i == (int)(double)i //相等
4、 f == (float)(double)i //相等

整数的加减

choiceTwoWay
零标志位为zf,溢出标志位位of;
符号标志sf,进/借位标志cf
zf=1时,表示结果为0;
of=1时,表示带符号整数的加减法运算发生溢出;
cf=Sub异或C

小结:

这章也为我解释了为什么int型到达一定的位数之后会由正数变成负数。溢出让我更清晰的明白了其中的原理。还有浮点数,让我明白了大数加1之后并没有什么变化的最基础原理。IEEE754标准也让我更加清晰了浮点数的存储方式,以及其工作原理。

1…34
gdccwxx

gdccwxx

39 日志
16 标签
GitHub E-Mail
© 2017 — 2022 gdccwxx