javascript简介、如何应用、数据类型及应用、变量
(一)javascript 简介
能在静态HTML页面上添加一些动态效果;事实上JavaScript除了语法上有点像Java,其他部分基本上没啥关系。
(二)javascript在html中的应用
JavaScript代码可以直接嵌在网页的任何地方,不过通常我们都把JavaScript代码放到<head>
中:
<head>
<script>
alert('Hello, world');
</script>
</head>
第二种方法是把JavaScript代码放到一个单独的.js
文件,然后在HTML中通过<script src="..."></script>
引入这个文件:
<head>
<script src="/static/js/abc.js"></script>
</head>
这样,/static/js/abc.js
就会被浏览器执行。
把JavaScript代码放入一个单独的.js
文件中更利于维护代码,并且多个页面可以各自引用同一份.js
文件。
可以在同一个页面中引入多个.js
文件,还可以在页面中多次编写<script> js代码... </script>
,浏览器按照顺序依次执行。
(三)基本语法
1,每个语句以;
结束,语句块用{...} 比如语句块:
if (2 > 1) {
x = 1;
y = 2;
z = 3;
}
注意花括号{...}
内的语句具有缩进,通常是4个空格。缩进不是JavaScript语法要求必须的,但缩进有助于我们理解代码的层次,所以编写代码时要遵守缩进规则。很多文本编辑器具有“自动缩进”的功能,可以帮助整理代码。
2,注释:注释一行用//;注释多行用/* */
(四)数据类型
js中,数据具有属性和方法。例如string具有length属性和match方法。
变量
1.数字
Number
JavaScript不区分整数和浮点数,统一用Number表示,以下都是合法的Number类型:
123; // 整数123
0.456; // 浮点数0.456
1.2345e3; // 科学计数法表示1.2345x1000,等同于1234.5
-99; // 负数
NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示
Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就表示为Infinity
2.字符串
字符串是以单引号'或双引号"括起来的任意文本,比如'abc'
,"xyz"
等等。请注意,''
或""
本身只是一种表示方式,不是字符串的一部分,因此,字符串'abc'
只有a
,b
,c
这3个字符。
1.普通字符串:如果'
本身也是一个字符,那就可以用""
括起来,比如"I'm OK"
包含的字符是I
,'
,m
,空格,O
,K
这6个字符。如果字符串内部既包含'
又包含"
怎么办?可以用转义字符\
来标识,比 如:'I\'m \"OK\"!'(即:在一个字符串中,每个'或者"前面加一个\即可,例如'm前面加了\,\"OK\"中,前面的"和后面的"的前面都加了\);
表示的字符串内容是:I'm "OK"!
2.多行字符串
由于多行字符串用\n
写起来比较费事,所以最新的ES6标准新增了一种多行字符串的表示方法,用` ... `表示:
`这是一个
多行
字符串`;
3.多个字符串串连成一个字符串:要把多个字符串连接起来,可以用+
号连接:
var name = '小明';
var age = 20;
var message = '你好, ' + name + ', 你今年' + age + '岁了!';
alert(message);
如果有很多变量需要连接,用+
号就比较麻烦。ES6新增了一种模板字符串,表示方法和上面的多行字符串一样,但是它会自动替换字符串中的变量:
var name = '小明';
var age = 20;
var message = `你好, ${name}, 你今年${age}岁了!`;
alert(message);
4.操作字符串(对于字符串的操作是改变不了字符串的,即字符串是不可以改变的,均返回新的字符串)
<1>获取字符串的长度:var s="hello, world!";
var Length=s.length; //通过函数length返回s的长度值:13,将该返回值符给Length
<2>获取字符串中的某个指定位置的字符,则用数组下标的方式获取:var s="hello, world!";
s[0]; // 'H'
s[6]; // ' '
s[7]; // 'w'
s[12]; // '!'
s[13]; // undefined 超出范围的索引不会报错,但一律返回undefined
要特别注意:若在上例中,我们将s[0]=“w”;那么s字符串的值仍然为"hello, world!"
<3>JavaScript为字符串提供了一些常用方法,注意,调用这些方法本身不会改变原有字符串的内容,而是返回一个新字符串:
toUpperCase:将字符串全部变为大写字符串;
var s = 'Hello';
var t = s.toUpperCase(); //
s.toUpperCase()
返回'HELLO',将该返回值符给变量t;
toLowerCase:把一个字符串全部变为小写:
var s = 'Hello';
var lower = s.toLowerCase(); // 返回'hello'并赋值给变量lower
indexOf() 方法
:indexOf()
会搜索指定字符串出现的位置:
var s = 'hello, world';
var t = s.indexOf('world'); // 返回7,t的值为7:即word字符串中的首字母w存在s[7]中 ,固返回数字7
var w = s.indexOf('World'); // w的值为-1:字符串没有找到指定的子串,返回-1
lastIndexOf() 方法
substring:substring()
返回指定索引区间的子串:
var s = 'hello, world'
s.substring(0, 5); // 从索引0开始到5(不包括5),返回'hello'
s.substring(7); // 从索引7开始到结束,返回'world'
charAt() 方法
定义和用法
charAt() 方法可返回指定位置的字符。
请注意,JavaScript 并没有一种有别于字符串类型的字符数据类型,所以返回的字符是长度为 1 的字符串。
concat() 方法
定义和用法
concat() 方法用于连接两个或多个字符串。
match() 方法
匹配指定的字符串
正则表达式的匹配
replace() 方法
定义和用法
split() 方法
定义和用法
split() 方法用于把一个字符串分割成字符串数组。
这里第一句话,有空格来分,结果在输出中,空格变成了,字符串数组中不存在空格。
以什么来分,什么就不在字符数组中显示。例如,以a来分
也就是说,以什么来分,什么就变为字符串中的逗号,即字符串数组中没有该字符串。
substr() 方法
定义和用法
substr() 方法可在字符串中抽取从 start 下标开始的指定数目的字符。
语法
stringObject.substr(start,length)
replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
3.布尔值
一个布尔值只有true
、false
两种值,要么是true
,要么是false
,可以直接用true
、false
表示布尔值,也可以通过布尔运算计算出来: 两种运算符可以产生布尔值:比较运算符与逻辑 运算符
逻辑运算符(&&,||,!):&&:a&&b,只有a和b均为true,该表达式结果才为true否则为false;||:a||b,只要a、b中有一个为true,该表达结果就为true否则为 false; !:!若a为true,则!a结果为false否则相反;
比较运算符(>、<、==、===、!= ):==与===的区别:
第一种是==
比较,它会自动转换数据类型再比较,很多时候,会得到非常诡异的结果;
第二种是===
比较,它不会自动转换数据类型,如果数据类型不一致,返回false
,如果一致,再比较。
由于JavaScript这个设计缺陷,不要使用==
比较,始终坚持使用===
比较
另一个例外是NaN
这个特殊的Number与所有其他值都不相等,包括它自己:
NaN === NaN; // false
唯一能判断NaN
的方法是通过isNaN()
函数:
isNaN(NaN); // true
最后要注意浮点数的相等比较:
1 / 3 === (1 - 2 / 3); // false
这不是JavaScript的设计缺陷。浮点数在运算过程中会产生误差,因为计算机无法精确表示无限循环小数。要比较两个浮点数是否相等,只能计算它们之差的绝对值,看是否小于某个阈值:
Math.abs(1 / 3 - (1 - 2 / 3)) < 0.0000001; // true
4. 数组
定义数组
数组对象用来在单独的变量名中存储一系列的值。
我们使用关键词 new 来创建数组对象。下面的代码定义了一个名为 myArray 的数组对象:
var myArray=new Array()
有两种向数组赋值的方法(你可以添加任意多的值,就像你可以定义你需要的任意多的变量一样)。
数组是一组按顺序排列的集合,集合的每个值称为元素。JavaScript的数组可以包括任意数据类型。例如:[1, 2, 3.14, 'Hello', null, true];
上述数组包含6个元素。数组用[]
表示,元素之间用,
分隔。
另一种创建数组的方法是通过Array()
函数实现:new Array(1, 2, 3); // 创建了数组[1, 2, 3]
然而,出于代码的可读性考虑,强烈建议直接使用[]
。
数组的元素可以通过索引来访问。请注意,索引的起始值为0
:
var arr = [1, 2, 3.14, 'Hello', null, true];
arr[0]; // 返回索引为0的元素,即1
arr[5]; // 返回索引为5的元素,即true
arr[6]; // 索引超出了范围,返回undefined
操作数组
<1> 获取数组的长度:数组名.length //返回数组长度如:6
var arr=[0,'hello',true,null,7,9];
var len=arr.length; //arr.length将会返回一个值即6,并且将arr.length的值赋给len
document.write(len);
<2> 修改数组的长度(变长和缩短)
变长: var arr=[0,'hello',true,null,7,9];
arr.length=7; //将数组arr的长度变为7,此时arr数组为[0,'hello',true,null,7,9,undefined];
var arr=[0,'hello',true,null,7,9];//
arr[6]=6;//这相当于将arr的长度变为7,此时arr数组为[0,'hello',true,null,7,9,6];
变短: var arr=[0,'hello',true,null,7,9];
arr.length=3;//将数组的长度变为3,此时arr数组为[0,'hello',true]
<3> 修改数组元素的值(Array
可以通过索引把对应的元素修改为新的值,因此,对Array
的索引进行赋值会直接修改这个Array
)
var arr=[0,'hello',true,null,7,9];
arr[0]=1;//此时arr数组为 [1,'hello',true,null,7,9]
<4> 搜索一个指定的元素的位置:indexOf() 用法:数组名.indexOf(某元素值);
var arr=[0,'hello',true,null,7,9,0];
arr.indexOf(0);//返回值为0的元素的下表值,即0;
arr.indexOf('hello');//返回值为‘hello’的元素的下表值,即1;
indexOf
总是返回第一个元素的位置,数组最后一个0,返回的元素位置不是6,而是0;
<5> 截取Array
的部分元素,然后返回一个新的Array
:slice() 用法: 数组名.slice(开始下标值,截止下标值) 不会改变原数组
var arr=[0,'hello',true,null,7,9];
arr.slice(3,5);//从下标值为3开始即从arr[3]开始至arr[5]之前的元素,执行后,返回新的数组[null,7]
arr.slice(3);//截取从arr[3]开始的所有元素,执行后,返回新的数组[null,7,9]
arr.slice();//相当于复制整个数组
<6>向数组的尾部增加和删除元素:增加:push();删除pop(); push和pop改变原数组
增加元素:push() 用法:数组名.push(元素值1,元素值2,元素值3......)
var arr=[0,'hello',true,null,7,9];
arr.push(3,11,'helloword','wo');//向数组arr的后面增加元素,然后, arr.push(3,11,'helloword','wo')返回arr现在的长度值,11
arr;//此时的arr为[0,'hello',true,null,7,9,3,11,'helloword','wo']
删除元素:pop() 用法:数组名.pop();
var arr=[0,'hello',true,null,7,9];
arr.pop();//pop()返回被删除的元素即9
arr.pop(); arr.pop(); arr.pop(); // 再连续pop 3次
arr//此时arr为[0,'hello']
<7>向数组的头部增加和删除元素:增加:unshift()和删除:shift()
var arr = [1, 2];
arr.unshift('A', 'B'); // 返回Array新的长度: 4
arr; // ['A', 'B', 1, 2]
arr.shift(); // 'A'
arr; // ['B', 1, 2]
arr.shift(); arr.shift(); arr.shift(); // 连续shift 3次
arr; // []
arr.shift(); // 空数组继续shift不会报错,而是返回undefined
arr; // []
<8>给数组进行默认排序:sort() 用法:数组名.sort();特点:改变arr数组
var arr = [8,2,10,6];
arr.sort();//将arr重新排序,并将排序后的arr数组返回
<9>reverse:把整个Array
的元素给掉个个,也就是反转:
var arr = ['one', 'two', 'three'];
arr.reverse();
arr; // ['three', 'two', 'one']
<10>
splice
splice()
方法是修改Array
的“万能方法”,它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素:
var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];
// 从索引2开始删除3个元素,然后再添加两个元素:
arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL', 'Excite']
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
// 只删除,不添加:
arr.splice(2, 2); // ['Google', 'Facebook']
arr; // ['Microsoft', 'Apple', 'Oracle']
// 只添加,不删除:
arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
合并两个数组 - concat()concat()
方法并没有修改当前Array
,而是返回了一个新的Array
用数组的元素组成字符串 - join() 和string的slipt相反。
文字数组 - sort()
isArray() 方法
定义和用法
isArray() 方法用于判断一个对象是否为数组。
如果对象是数组返回 true,否则返回 false。
Math(算数)对象
floor():返回小于等于x的最大整数
reduce() 方法
定义和用法
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
reduce() 可以作为一个高阶函数,用于函数的 compose。
注意: reduce() 对于空数组是不会执行回调函数的。
例:var numbers=[65,44,12,4];
numbers.reduce(function(x1,x2){
return x1+x2;
})
在这里,第一步,X1为65,x2为44.第二步:x1为(65+44) 109,x2为12;第三步:x1为(109+12)121,x2为4.第四步:最后return 125(121+4)
如果上述reduce中除了回调函数还有参数,则第一步X1为 参数值,X2为65
js 数组 map方法
map
这里的map
不是“地图”的意思,而是指“映射”。[].map();
基本用法跟forEach
方法类似:
array.map(callback,[ thisObject]);
callback
的参数也类似:
[].map(function(value, index, array) {
// ...
});
map
方法的作用不难理解,“映射”嘛,也就是原数组被“映射”成对应新数组。下面这个例子是数值项求平方:
var data = [1, 2, 3, 4];
var arrayOfSquares = data.map(function (item) {
return item * item;
});
alert(arrayOfSquares); // [1, 4, 9, 16]
callback
需要有return
值,如果没有,就像下面这样:
var data = [1, 2, 3, 4];
var arrayOfSquares = data.map(function() {});
arrayOfSquares.forEach(console.log);
结果可以看到,数组所有项都被映射成了undefined
:
在实际使用的时候,我们可以利用map
方法方便获得对象数组中的特定属性值们。例如下面这个例子(之后的兼容demo也是该例子):
var users = [
{name: "张含韵", "email": "zhang@email.com"},
{name: "江一燕", "email": "jiang@email.com"},
{name: "李小璐", "email": "li@email.com"}
];
var emails = users.map(function (user) { return user.email; });
console.log(emails.join(", ")); // zhang@email.com, jiang@email.com, li@email.com
Array.prototype
扩展可以让IE6-IE8浏览器也支持map
方法:
if (typeof Array.prototype.map != "function") { Array.prototype.map = function (fn, context) { var arr = []; if (typeof fn === "function") { for (var k = 0, length = this.length; k < length; k++) { arr.push(fn.call(context, this[k], k, this)); } } return arr; };
5.对象
(Object.create(o),如果o是一个字面量对象或实例对象,那么相当于是实现了对象的浅拷贝)
JavaScript 中的所有事物都是对象:字符串、数值、数组、函数...
此外,JavaScript 允许自定义对象。
(一)对象的定义:
对象只是带有属性和方法的特殊数据类型。
JavaScript的对象是一组由键-值组成的无序集合,
除了字符串、数值、数组、函数等对象以外,一般我们可以自己定义对象,
例如:
var person = {
name: 'Bob',
age: 20,
tags: ['js', 'web', 'mobile'],
city: 'Beijing',
hasCar: true,
zipcode: null
};
(二)对象的键值类型
JavaScript对象的键都是字符串类型,值可以是任意数据类型。上述person
对象一共定义了6个键值对,其中每个键又称为对象的属性,例如,person
的name
属性为'Bob'
,zipcode
属性为null
。
要获取一个对象的属性,我们用对象变量.属性名
的方式:
person.name; // 'Bob'
person.zipcode; // null
(三)创建对象
1.直接创建某个对象;(不需要创建实例,该对象就是一个具体的对象)2.利用构造器来创建对象(具体的实例来引用对象)
2.使用构造器来创建对象
(四)对象中的属性和方法的添加
1.直接添加
在定义的时候就直接添加
var obj={
name:"zhangsan",
age:18
}
2.通过赋值的方式添加
obj.name="张三"
3.使用defineProperty方法
- value:属性的值
- writable:如果为false,属性的值就不能被重写。
- get: 一旦目标属性被访问就会调回此方法,并将此方法的运算结果返回用户。
- set:一旦目标属性被赋值,就会调回此方法。
- configurable:如果为false,则任何尝试删除目标属性或修改属性以下特性(writable, configurable, enumerable)的行为将被无效化。
- enumerable:是否能在for...in循环中遍历出来或在Object.keys中列举出来。
TheObject.defineProperty()方法定义了一个新的属性直接在一个对象,或修改现有的属性的对象,并返回该对象。
Object.defineProperty(obj,prop,descriptor)
obj:定义的对象属性。
prop:属性的名称定义或修改。
descriptor:该属性被定义或修改描述符。
可使用 Object.defineProperty 函数来执行以下操作:
向对象添加新属性。 当对象不具有指定的属性名称时,发生此操作。
修改现有属性的特性。 当对象已具有指定的属性名称时,发成此操作。
描述符对象中会提供属性定义,用于描述数据属性或访问器属性的特性。 描述符对象是 Object.defineProperty 函数的参数。
添加数据属性
在以下示例中,Object.defineProperty 函数向用户定义的对象添加数据属性。
var obj = {};
Object.defineProperty(obj, "newDataProperty", {
value: 101, //属性默认值
writable: true, //是否可以对属性重新赋值
enumerable: true, //是否可以枚举,是否可以通过for...in遍历到,是否可以通过Obejct.key()方法获取属性名称
configurable: true //是否可以删除属性,是否可以修改属性的writable、enumerable、configurable属性<pre name="code" class="html"> set:undefined, //属性被赋值时方法调用
get:undefined //属性被读取时方法调用
obj
.newDataProperty的值为101.
修改数据属性
若要修改对象的属性特性,请将以下代码添加到前面所示的 addDataProperty 函数。 descriptor 参数只包含 writable 特性。 其他数据属性特性保持不变。
get和set方法(set是存储对象属性触发的方法,get是读取对象属性触发的方法)
get :获取指定属性的值的时候会触发该方法;
set:给某个属性赋值时会触发该方法;
var a = {};
Object.defineProperty(a,"bloger",{get:function(){return "司徒正美"}});
alert(a.bloger);
var obj = {};
Object.defineProperty(obj, "newAccessorProperty", {
set: function (x) {
document.write("in property set accessor" + newLine);
this.newaccpropvalue = x;
},
get: function () {
document.write("in property get accessor" + newLine);
return this.newaccpropvalue;
},
enumerable: true,
configurable: true
});
obj.newAccessorProperty = 30;
document.write("Property value: " + obj.newAccessorProperty + newLine);
// Output:
// in property set accessor
// in property get accessor
// Property value: 30
(五)对象的属性和方法的操作
<1>访问属性
访问属性是通过.
操作符完成的,但这要求属性名必须是一个有效的变量名。如果属性名包含特殊字符,就必须用''
括起来:
var xiaohong = {
name: '小红',
'middle-school': 'No.1 Middle School'
};
1.通过.
操作符完成
xiaoming.name;
xiaohong.name; // '小红'
2.通过[]操作完成(注意:[key]里面是字符串。)
xiaohong['name']; //
'小红'
xiaohong
的属性名middle-school
不是一个有效的变量,就需要用''
括起来。访问这个属性也无法使用.
操作符,必须用['xxx']
来访问:
xiaohong['middle-school']; //
'No.1 Middle School'
例:var obj={};
要将var name="zhangsan"设置为obj的属性;‘lisi
’设置为"zhangsan"的值,则:
必须用obj[name]="lisi"而不能用obj.name="lisi".
<2>增加、删除属性
由于JavaScript的对象是动态类型,你可以自由地给一个对象添加或删除属性:
var xiaoming = {
name: '小明'
};
xiaoming.age; // undefined
xiaoming.age = 18; // 新增一个age属性
xiaoming.age; // 18
delete xiaoming.age; // 删除age属性
xiaoming.age; // undefined
delete xiaoming['name']; // 删除name属性
xiaoming.name; // undefined
delete xiaoming.school; // 删除一个不存在的school属性也不会报错
<3>
要判断一个属性是否是xiaoming
自身拥有的,而不是继承得到的,可以用hasOwnProperty()
方法:
var xiaoming = {
name: '小明'
};
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('toString'); // false
遍历对象中的键值(JavaScript for...in 循环)
prototype 对象
面向对象编程很重要的一个方面,就是对象的继承。A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法。这对于代码的复用是非常有用的。
大部分面向对象的编程语言,都是通过“类”(class)来实现对象的继承。JavaScript 语言的继承则是通过“原型对象”(prototype)。
原型对象概述
构造函数的缺点
JavaScript 通过构造函数生成新对象,因此构造函数可以视为对象的模板。实例对象的属性和方法,可以定义在构造函数内部。
function Cat (name, color) {
this.name = name;
this.color = color;
}
var cat1 = new Cat('大毛', '白色');
cat1.name // '大毛'
cat1.color // '白色'
上面代码中,Cat
函数是一个构造函数,函数内部定义了name
属性和color
属性,所有实例对象(上例是cat1
)都会生成这两个属性,即这两个属性会定义在实例对象上面。
通过构造函数为实例对象定义属性,虽然很方便,但是有一个缺点。同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。
function Cat(name, color) {
this.name = name;
this.color = color;
this.meow = function () {
console.log('喵喵');
};
}
var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');
cat1.meow === cat2.meow
// false
上面代码中,cat1
和cat2
是同一个构造函数的两个实例,它们都具有meow
方法。由于meow
方法是生成在每个实例对象上面,所以两个实例就生成了两次。也就是说,每新建一个实例,就会新建一个meow
方法。这既没有必要,又浪费系统资源,因为所有meow
方法都是同样的行为,完全应该共享。
这个问题的解决方法,就是 JavaScript 的原型对象(prototype)。
prototype 属性的作用
JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。
下面,先看怎么为对象指定原型。JavaScript 规定,每个函数都有一个prototype
属性,指向一个对象。
function f() {}
typeof f.prototype // "object"
上面代码中,函数f
默认具有prototype
属性,指向一个对象。
对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。
function Animal(name) {
this.name = name;
}
Animal.prototype.color = 'white';
var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');
cat1.color // 'white'
cat2.color // 'white'
上面代码中,构造函数Animal
的prototype
属性,就是实例对象cat1
和cat2
的原型对象。原型对象上添加一个color
属性,结果,实例对象都共享了该属性。
原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。
Animal.prototype.color = 'yellow';
cat1.color // "yellow"
cat2.color // "yellow"
上面代码中,原型对象的color
属性的值变为yellow
,两个实例对象的color
属性立刻跟着变了。这是因为实例对象其实没有color
属性,都是读取原型对象的color
属性。也就是说,当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。这就是原型对象的特殊之处。
如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
cat1.color = 'black';
cat1.color // 'black'
cat2.color // 'yellow'
Animal.prototype.color // 'yellow';
上面代码中,实例对象cat1
的color
属性改为black
,就使得它不再去原型对象读取color
属性,后者的值依然为yellow
。
总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。
Animal.prototype.walk = function () {
console.log(this.name + ' is walking');
};
上面代码中,Animal.prototype
对象上面定义了一个walk
方法,这个方法将可以在所有Animal
实例对象上面调用。
原型链
JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……
如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype
,即Object
构造函数的prototype
属性。也就是说,所有对象都继承了Object.prototype
的属性。这就是所有对象都有valueOf
和toString
方法的原因,因为这是从Object.prototype
继承的。
那么,Object.prototype
对象有没有它的原型呢?回答是Object.prototype
的原型是null
。null
没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null
。
读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype
还是找不到,则返回undefined
。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。
注意,一级级向上,在整个原型链上寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
举例来说,如果让构造函数的prototype
属性指向一个数组,就意味着实例对象可以调用数组方法。
(五)变量
变量的命名规则:大小写英文、数字、$
和_
的组合,且不能用数字开头。变量名也不能是JavaScript的关键字,如if
、while
等。申明一个变量用var
语句,比如:
var a; // 申明了变量a,此时a的值为undefined
var $b = 1; // 申明了变量$b,同时给$b赋值,此时$b的值为1
var s_007 = '007'; // s_007是一个字符串
var Answer = true; // Answer是一个布尔值true
var t = null; // t的值是null
可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量,但是要注意只能用var
申明一次,例如:
var a = 123; // a的值是整数123
a = 'ABC'; // a变为字符串
strict模式
JavaScript在中,如果一个变量没有通过var
申明就被使用,那么该变量就自动被申明为全局变量:
i = 10; // i现在是全局变量
在同一个页面的不同的JavaScript文件中,如果都不用var
申明,恰好都使用了变量i
,将造成变量i
互相影响,产生难以调试的错误结果。
使用var
申明的变量则不是全局变量,它的范围被限制在该变量被申明的函数体内(函数的概念将稍后讲解),同名变量在不同的函数体内互不冲突。
为了修补JavaScript这一严重设计缺陷,ECMA在后续规范中推出了strict模式,在strict模式下运行的JavaScript代码,强制通过var
申明变量,未使用var
申明变量就使用的,将导致运行错误。
启用strict模式的方法是在JavaScript代码的第一行写上:
'use strict';