推荐js书《JavaScript高级程序设计(第二版)》(美.泽卡斯)
js用来负责页面的交互,但是实际开发中一般使用js框架,比如:Prototype.js(用的不多)、Jquery
【数据类型】:
l 只有一个类型:var,js是动态语言(动态语言比如Python、Ruby,它们都是没有变量类型的,而且,是一行一行解释执行的;静态语言如Java、C有变量类型,且编译执行的)。
l 函数内部没有用var声明的变量会被当做全局变量,且会覆盖外面的同名变量(注意执行到这个变量时候,这个全局变量才会被创建);
【最佳实践】:
函数内部定义的变量一定要加上var。
l 因为js中变量的是不分“类型”的,如果想知道改变量的具体类型的话可以使用
typeof a来查看:alert ( typeof a )
l JS中常用的数据类型有:
Number(如10、10.6都是Number类型);
String
Array
Date
Math
l 类型转换:
var a = “11”;
alert()
l
【例1】:
var a = 11;
alert(a+1);//输出12
【例2】:
var b = "11";
//将字符串转成数字
alert( Number(a) + 1 )//输出111
【例3】:
var c = "hello";
//若将字符串转成数字会提示NaN
alert( Number(c) );//提示框上显示NaN
【例=4】:
var d = "12px";
//使用parseInt可以将字符串开头的几个数字转换成int
//但是,如果开头不是数字就会提示NaN
alert(parseInt(12)+1);//显示13
【例5】:
var e = ["a","b","c",1, 2, 3];
//对于数组而言,显示的类型结果就是object不会显示、Array
alert(typeof a);//提示object
alert(e instanceof Array)//判断e是否是数组
function fn1(){}
alert(typeof fn1)//提示function
【例6】:
Ø 6.1
//布尔类型:在js中非0就是true
//特殊情况:NaN、undefined、0是false
//看0是false还是true
alert(!!0);//提示false
alert(!!NaN);//提示false
//NaN实例运用:比如我们点击一次按钮就让size加1:
var size = "";
if(!parseInt(size)){//不能转
}else{//是数字
}
Ø 6.2
// 当一个变量没有定义值的时候,它是undefined
var size;//undefined
alert(!!size);//提示false
alert(size + 1)//提示NaN(因为size没有类型)
alert(size + "1")//提示undefined
【例1】:
js是使用function来定义对象的。
Ø 1.1先说说js中的函数定义:
function的定义方式:
方式一:
// 以下函数的内存模型是:定义了function后就在堆区分了一块内存:function(){alert("x")}
// 然后栈区有一个变量x指向堆区的这个函数
var x = function(){
alert("x");
}
x();//此时x就是一个function,这行代码执行后弹出x
方式二:
//以下函数的内存模型是:在堆去分配了一块区域存储:alert("fn");
//栈区有一个变量fn指向了该堆区的这个内存区域
//定义y=fn();表示y也指向堆中的内存区
function fn(){
alert("fn");
return "100";
}
var y = fn;
var z = fn();//将fn()执行的返回值赋给z
alert(y);//显示函数代码的内容
alert(z);//显示100
//以下两种方式都可以完成对函数fn的调用
//两种方式是一样的
y();
fn();
Ø 1.2 定义对象
//使用function定义Person对象(也就是类),注意参数不要写类型
//是用this为类定义属性:this.aaa(为类定义了aaa属性)
function Persion(name, age){
//为Person定义了属性:name,age,address
this.name = name;
this.age = age;
this.address = "NewYork";
//在类的内部定义方法:要用this,否则就不是类的变量,下面这个say()就错了
function say(){
alert("hello");
}
//同理,如果在类的内部定义 var n = 10,外部的p1是访问不到的
var n = 10;//外部的p1不能访问到n,应该用this.n = 10,它是作为类的局部变量
//给该类添加方法:
//(js的函数有一个问题,就是多个对象的实例都会有自己的函数内存空间可以
//通过Prototype解决)
this.say1 = function(){
alert("say1");
}
}
var p1 = new Person("Jack",18);//创建了对象p1
alert(p1.name);
alert(typeof p1);//提示object
alert(p1 instanceof Person);//true
alert(p1.say());//错误!提示say不是一个函数
alert(p1.say1());//正确!
//访问属性的方式除了上面的方式外,还可以用以下方式:
alert(p1["name"]+" | "+p1["address"]);
//在js中可以通过for in来遍历对象的属性:
for(var a in p1){
alert(a);//依次打印:name,age,address,say1
alert(p1.a);//这个不会打印属性值的,提示undefined
alert(p1[a]);//这个可以讲属性值打印出来(函数会将打印代码)
}
Date(可以参照帮助文档查看详细)
【例】:
var d = new Date();
//打印标准的日期格式
document.write(d);
//打印格式化的日期(在js中月是从0开始的):
document.write(d.getFullYear()+"年"+(d.getMonth()+1)+"月"+d.getDate()+"日 星期"+d.getDay());
String
(略:查看文档)
Array
js中的Array就是java中的list和stack的结合。
【例1】数组的定义方式:
//方式1:
var a = new Array();
a.push("dog");
//方式2:
a = new Array(11,22,33,44,"dog","cat");
alert(a);//打印数组中的内容
//方式3(用得较多):
a = [1,2,3,"dog","cat"];
//join:
alert(a);//打印a的内容(逗号分隔)
alert(a.join("---"));//打印a的内容(---分隔)
alert(a.sort());//sort只会通过字符串来排序
【例1】:点击事件
//调用js的click方法,参数this代表当前的这个标签
<input type="button" value="按钮" onclick="click(this)">
【例2】:鼠标事件:
<script type="text/javascript">
function mouseD(obj) {
//设置这个对象的颜色,在js中设置文本的样式均通过xx.style.样式名称
obj.style.color = "#f00";
//若使用代码来设置样式若在css中通过-表示的都用驼峰标识,
//font-size-->fontSize
obj.style.fontSize = "18px";
}
function outD(obj) {
obj.style.color = "#000";
obj.style.fontSize = "16px";
}
</script>
<div onmouseover="mouseD(this)" onmouseout="outD(this)">
鼠标移动上来试试
</div>
【例2】:with:
<script type="text/javascript">
with(document) {
//此时花括号中的所有代码都是基于document作为根对象,当使用write(xxx)就等于document.write(xxx);
write("aaa<br/>");
write("bbb<br/>");
write("ccc<br/>");
//使用alert也是允许,会先找document中的alert,找不到再从上一级找
alert("abc");
}
</script>
【例3】实例(点击文字让文字一直变大,大于30px时,就变小)(不重要!)
<script type="text/javascript">
var big = true;
function bigger(obj) {
var cs = parseInt(obj.style.fontSize);
if(cs) {
if(cs>=30) {
big = false;
obj.innerHTML = "点击变小";
}
if(cs<=12) {
big = true;
obj.innerHTML = "点击变大";
}
if(big) {
cs+=2;
} else {
cs-=2;
}
obj.style.fontSize = cs+"px";
} else {
obj.style.fontSize = "14px";
}
}
</script>
</head>
<body>
<div onclick="bigger(this)" style="cursor: pointer">点击变大</div>
</body>
【例4】:setTimeout点击后文字自动变大,然后点击一个按钮就停止(不重要!):
l 变大一次
<script type="text/javascript">
function cd() {
//3秒后执行bigger这个函数,setTimeout的意思即间隔一段时间就执行某个函数
//setTimeout仅仅只会执行一次,如果希望重复执行,需要使用setInterval
setTimeout("bigger()",3000);
}
function bigger() {
//获取html中节点的id为txt的节点
var node = document.getElementById("txt");
node.style.fontSize = "200px";
}
</script>
</head>
<body>
<div id="txt" style="cursor: pointer">开始</div>
<div onclick="cd()">点击开始操作</div>
</body>
l 连续变大
<script type="text/javascript">
var timeId;
function cd() {
//setInterval表示每隔一段时间就调用一次函数
timeId = setInterval("bigger()",500);
}
function sd(){
clearInterval(timeId);
}
function bigger() {
//获取html中节点的id为txt的节点
var node = document.getElementById("txt");
var size = parseInt(node.style.fontSize);
if(size) {
size+=10;
} else {
size = "14";
}
node.style.fontSize = size+"px";
}
</script>
</head>
<body>
<div id="txt">开始</div>
<div onclick="cd()" style="cursor: pointer">点击开始操作</div>
<div onclick="sd()" style="cursor: pointer">停止操作</div>
</body>
【例5】history.back()
返回到上一个页面,在onclick函数中直接调用这个函数即可,这种返回会传递表单中的数据。
【例6】:
alert()相当于window.alert();
window中还有:window.clolse();
window.print:打印
window.open:打开一个新窗口(比较常用!)
<a href="#"
onclick="window.open('test02.html','aaa',
'width=300,height=300,resizable=0')">
test02
</a>
<a href="#"
onclick="window.open('test03.html','aaa',
'width=400,height=400,resizable=0')">
test03
</a>
【注意】:
l 如果两个<a>的第二个参数(如aaa)相同的话会打开同一个窗口,不同的话,会分别打开自己的窗口。
l 这种弹出窗口会被屏蔽掉;
l 如果想要一个弹出窗口的效果的话现在一般使用div来实现(类似这样的思想)。
【例7】:弹出窗口输入祝福语,点击窗口上的输入按钮耨,将输入的内容显示在网页上,并关闭当前窗口。
<a href="#" onclick="window.open('bless.html','aaa','width=600,height=300')">输入你祝福语</a>
bless.html:
<script type="text/javascript">
function bless() {
//获取输入的祝福语
var mb = document.getElementById("mb").value;
//获取父类窗口
var p = window.opener;
//获取父类窗口中的id为bless的div
var pd = p.document.getElementById("bless");
//设置pd的值
pd.innerHTML = mb;
//关闭当前窗口
window.close();
}
</script>
</head>
<body>
输入祝福语:<input type="text" size="40" id="mb"/><input type="button" onclick="bless()" value="输入" />
</body>
节点的类型有:
元素节点(节点类型1):
属性节点(节点类型2):
文本节点(节点类型3):
注释节点(节点类型8):
文档节点(节点类型9):
<script type="text/javascript">
function getAllH1() {
var ah = document.getElementsByTagName("h1");
for(var i=0;i<ah.length;i++) {
//获取节点中的文本内容
alert(ah[i].innerHTML);
//获取节点的名称
alert(ah[i].nodeName);
//获取节点的类型
alert(ah[i].nodeType);
//获取节点中的值:节点中的值只是在针对文本节点时有用
alert(ah[i].nodeValue);
//获取某个节点的文本节点
alert(ah[i].firstChild.nodeType);
//获取某个文本节点的值,对于IE和firefox而言文本的空格不一致,
//对于IE而言,仅仅只会把换行加入空白,但是FF而言就是全部空格
//所以在获取文本节点值的时候,需要把空格去除
alert("|"+ah[i].firstChild.nodeValue+"|");
}
}
function getConH2() {
var con = document.getElementById("content");
var h2 = con.getElementsByTagName("h2");
//得到的h2元素是一个数组
alert(h2[0].innerHTML);//显示h2中的所有内容包括<span>
//通过h2这个节点来找到h3中span的值
//1、找到父节点
var pn = h2[0].parentNode;
//2、通过父节点找到名称为h3的节点
var h3 = pn.getElementsByTagName("h3")[0];
//3、通过h3找到span
var s = h3.getElementsByTagName("span")[0];
alert(s.innerHTML);
}
</script>
</head>
<body>
<div id="content"> //元素节点
<h1>
aaaaa1 //文本节点
<span>aaaaassss</span>
</h1>
<h2>
bbbbbbbbb1
<span>bbbbbssss</span>
</h2>
<h3>
cccccccc1
<span>ccccccssss</span>
</h3>
</div>
<h1>
hhhhhhhaaaaa1
<span>hhhhhhhhhhhaaaaassss</span>
</h1>
<h2>
hhhhhhhhhhbbbbbbbbb1
<span>hhhhhhbbbbbssss</span>
</h2>
<h3>
hhhhhhhhhhhcccccccc1
<span>hhhhhhhhhhhhhccccccssss</span>
</h3>
<input type="button" value="获取所有的h1" onclick="getAllH1()" />
<input type="button" value="获取content的h2" onclick="getConH2()" />
</body>
innerHTML是对元素节点使用的,如上面的<span>aaaaassss</span>
【说明】:
前面的我们的事件都是放在html代码里的,这样的最大的问题是,相当于将js代码和html代码融合在了一起了,这样不好。
【例1】:
<body>
<ul>
<li>aaaaaaaaaaaaaa</li>
<li>bbbbbbbbbbbbbb</li>
<li>cccccccccccccc</li>
<li>dddddddddddddd</li>
<li>eeeeeeeeeeeeee</li>
</ul>
<input type="button" value="点击一下" id="btn"/>
<script type="text/javascript">
var btn = document.getElementById("btn");
//可以通过如下方式来绑定事件,就可以完成事件和html的解耦合操作
//在开发中通常都是使用这种方式来绑定事件
//这个事件的处理函数中默认有一个event的参数,用来获取相应的事件信息
btn.onclick = function(event) {
//注意:对于IE不会自动传递event参数,IE需通过window.event获取事件
//但是FF却不支持window.event,所以通常使用如下方式解决
//如果event为空(undefined为false)就会取window.event
event = event||window.event;
alert(event.type);//类型为click,显示click
//this就表示这个按钮对象
alert(this.value);
}
</script>
</body>
【例2】:在js中获取html中的元素的时候,有时候html还没加载完,这时候就获取不到html中的元素,解决方法是可以在html(即Document)加载完成后再执行js。
方法是:
body标签上加上<body onload=”init()”>或者将这个函数也写到js中:window.onload=init
l 给按钮添加点击事件:
<script type="text/javascript">
//当窗口加载完毕之后才执行init方法,这样可以省略body中的onload
//所以如果希望使用如下的事件定义方式,需要先完成html信息的加载
window.onload = init;
/*此时init方法是在body的onload之后执行,就等于在所有的页面标签加载完毕之后才执行init,此时节点就存在了*/
function init() {
alert("abc");
var btn = document.getElementById("btn");
/*如下绑定方式带来最大的一个问题是如果将该段代码放到head中定义,在执行到绑定事件的时候并没有把html的标签渲染出来,所以通过DOM得到的节点都是null的,就报错了,解决这个问题的方法是在页面加载完成之后才调用以下这段代码可以在body中的通过onload事件来处理*/
btn.onclick = function(event) {
event = event||window.event;
alert(event.type);
alert(this.value);
}
}
</script>
</head>
<body>
<input type="button" value="点击一下" id="btn"/>
</body>
【例3】:给li添加鼠标事件(移上和移开改变颜色):
<script type="text/javascript">
window.onload = init;
function init() {
//1、找到所有的li
var lis = document.getElementsByTagName("li");
//2、为所有的li绑定事件
for(var i=0;i<lis.length;i++) {
lis[i].onmouseover = changeColor;
lis[i].onmouseout = reColor;
}
}
function changeColor() {
this.style.color = "#f00";
}
function reColor() {
this.style.color = "#000";
}
</script>
</head>
<body>
<ul>
<li>aaaaaaaaaaaaaa</li>
<li>bbbbbbbbbbbbbb</li>
<li>cccccccccccccc</li>
<li>dddddddddddddd</li>
<li>eeeeeeeeeeeeee</li>
</ul>
</body>
【例3】:显示下啦菜单:
<style type="text/css">
*{
padding: 0px;
margin: 0px;
font-size:12px;
}
#menu_bar {
position: absolute;
left:50px;
top:50px;
}
dl.menu {
float:left;
width:120px;
}
dl.menu dt,dl.menu dd {
height:30px;
background: #339;
color:#fff;
border-right:#ffffff 1px solid;
text-align: center;
}
dl.menu dt span {
position: relative;
top:6px;
}
dl.menu dd {
background: #911;
color:#fff;
border-bottom:#ffffff 1px solid;
display: none;
}
dl.menu dd a {
position: relative;
top:6px;
}
a.menu_href:link,a.menu_href:visited {
text-decoration: none;
color:#fff;
}
dl.menu dd:hover {
background:#393;
cursor:pointer;
}
</style>
<script type="text/javascript">
window.onload = function(){
//1、找到所有dl
var dls = document.getElementById("menu_bar").getElementsByTagName("dl");
for(var i=0;i<dls.length;i++) {
//为所有dl绑定事件
dls[i].onmouseover = show;
dls[i].onmouseout = hidden;
}
};
function show() {
/*这种方式存在事件冒泡(移到dd上会将事件冒泡到dl上,触发移出移进事件),会多次调用,严重影响效率(只要移到dd上就会执行show方法),JQUery等框架可解决这样的问题*/
var c = document.getElementById("content");
c.innerHTML+="2";
//1、找到dd
var dds = this.getElementsByTagName("dd");
for(var i=0;i<dds.length;i++) {
dds[i].style.display = "block";
}
}
function hidden() {
var c = document.getElementById("content");
c.innerHTML+="1";
//1、找到dd
var dds = this.getElementsByTagName("dd");
for(var i=0;i<dds.length;i++) {
dds[i].style.display = "none";
}
}
</script>
</head>
<body>
<div id="content"></div>
<div id="menu_bar">
<dl class="menu">
<dt><span>文件管理</span></dt>
<dd><a href="#" class="menu_href">打开文件</a></dd>
<dd><a href="#" class="menu_href">删除文件</a></dd>
<dd><a href="#" class="menu_href">存储文件</a></dd>
<dd><a href="#" class="menu_href">关闭文件</a></dd>
<dd><a href="#" class="menu_href">退出</a></dd>
</dl>
</div>
</body>
l 后面如果我们想熟练的用好js框架,就必须将js的对象掌握清楚,否则不仅用不好,而且,出了错也不知道如何解决。
【我的小结】:
函数一般情况下就可以看成是一个对象。函数和对象的区别是:
对象是通过引用的指向完成对象的赋值的,函数是通过对象的拷贝来完成对象的赋值的。
function可以用来定义一个对象(对象的类型也是Function类型的)。
l 函数是一个非常特殊的对象,是Function类的实例,在内存中存储的操作是通过一对键值对来存储的;
l 函数名是“键”,其指向了内存中的一个对象,该对象是Function类型的对象,对象的内容是“值”,也就是函数的内容。
//函数的定义方式一:
function fn1() {
alert("fn1");
}
alert(typeof fn1); //Function
//由于函数是一个对象,所以可以通过如下方式定义
//以下是通过函数的拷贝来完成赋值,两个引用并没有指向同一个对象
var fn2 = fn1;//将fn1指向的函数在内存中拷贝一份,让fn2指向它
//函数的定义方式二:
fn2();//调用fn2
fn1 = function() {
alert("fnn1");
}
【例】:
<script type="text/javascript">
function sum(num1,num2) {
return num1+num2;
}
var sum = function(num1,num2) {
return num1+num2;
}
function sum(num1) {
return num1+100;
}
var sum = function(num1) {//sum被重新赋值,重新指向了新的内存空间
return num1+100;
}
//以下两个都显示19,原因是,如果将上面的函数写成标志了删除线的形式后就会明显看
//到原因了(分析内存结构)
alert(sum(19)); //打印119
alert(sum(19,20)); //打印119
</script>
【提示】:
l 函数的参数和调用没有关系,如果函数只有一个参数,但是却传入了两个参数,仅仅只会匹配一个,所以在js中函数不存在重载!
因为函数就是对象,所以函数还有如下定义方式:
/*
定义方式(三)等于这样定义了一个函数:
function fn(num1,num2){
alert(num1+num2);
}
所以通过以下的例子,充分的说明函数就是一个对象
*/
//函数有如下一种定义方式(三)
var fn = new Function("num1","num2","alert('fun:'+(num1+num2))");
fn(12,22);
理解框架时非常有用!
/*由于函数是对象,所以可以直接把函数通过参数传递进来*/
function callFun(fun,arg) {
//第一个参数就是函数对象
return fun(arg);
}
function say(str) {
alert("hello "+str);
}
//调用了say函数
callFun(say,"Leon");//效果和say(“Leon”)一样
function fn1(arg) {
/*此时返回的是一个函数对象*/
var rel = function(num) {
return arg+num;
}
return rel;
}
//此时f是一个函数对象,可以完成调用
var f = fn1(20); //这个返回的是fn1中定义的函数
alert(f(20)); //f是fn1中定义的函数,所以这句话是向fn1中的这个函数传参数
alert(f(11)); //同上
【例1】:按照数字排序:
sort默认是按字符串来排序的。
/*根据数字来进行排序的函数*/
function sortByNum(a,b) {
return parseInt(a)-parseInt(b);
}
alert("11"+1);
//当进行减法的时候,会自动完成转换
alert("11"-1);
var as = [1,2,"11px",33,"12px",190];
as.sort();//默认是按照字符串来排序的
//对于js而言,默认是按照字符串来进行排序的
as.sort(sortByNum); //使用函数来做数字排序
alert(as);
【例2】:按对象排序
2.1 使用一般方式排序
//测试根据对象排序
function Person(name,age) {
this.name = name;
this.age = age;
}
var p1 = new Person("Leno",39);
var p2 = new Person("John",23);
var p3 = new Person("Ada",41);
var ps = [p1,p2,p3];
ps.sort(sortByAge);
//取得属性值的方法p1.name,p1["name"]
/*使用以下方法来处理排序,带来的问题是需要为每一个属性都设置一个函数,显然不灵活但是如果通过函数的返回值调用就不一样了(见2.2)*/
function sortByName(obj1,obj2) {
if(obj1.name>obj2.name) return 1;
else if(obj1.name==obj2.name) return 0;
else return -1;
}
function sortByAge(obj1,obj2) {
return obj1.age-obj2.age;
}
2.2 使用返回函数的方式
//测试根据对象排序
function Person(name,age) {
this.name = name;
this.age = age;
}
var p1 = new Person("Leno",39);
var p2 = new Person("John",23);
var p3 = new Person("Ada",41);
var ps = [p1,p2,p3];
ps.sort(sortByProperty("age"))
function sortByProperty(propertyName) {
var sortFun = function(obj1,obj2) {
if(obj1[propertyName]>obj2[propertyName]) return 1;
else if(obj1[propertyName]==obj2[propertyName])return 0;
else return -1;
}
return sortFun;
}
function show() {
var p = document.getElementById("person");
for(var i=0;i<ps.length;i++) {
p.innerHTML+=ps[i].name+","+ps[i].age+"<br/>";
}
}
show();
函数对象中有一个内置的属性,arguments,它是一个数组,它存储了向函数传递来的所有参数。
比如,函数function fn1(num){ alert(num) }只有一个参数,但是我们可以向fn1传递3个参数,当然了,在alert中显示一个值,但是我们在fn1内部可以通过arguments这个数组获取到传递的这3个值。
【例1】:
function say(num) {
/*在函数对象中有一个属性叫做arguments,通过这个属性可以获取相应的参数值,这个属性是一个数组,其实就是传递进来的参数*/
alert(arguments.length);
for(var i=0;i<arguments.length;i++) {
alert(arguments[i]);
}
alert(num);
}
【例2】:
arguments这个对象中有一个callee方法,arguments.callee(arg)可以做反向的调用。
l 比如我们平时写求阶乘的函数是这么写:
//求阶乘的函数
function fac(num){
if(num<=1){
return 1;
}else{
return num*fac(num-1);
}
}
这样写阶乘函数有一个问题,因为fac内部也用到了fac这个函数名,假如我们在其他地方使用了函数fac:
var fn = fac;
然后我们可以使用fn做递归操作:
fn(5);//这时候不会有任何问题,如果在此行前让fac=null,就会报错了,可以使用arguments.callee解决,在js中通常都是使用这种方式做递归的!
l 我们使用arguments.callee可以这么写:
function factorial(num) {
if(num<=1) return 1;
//此时和函数名耦合在一起
// else return num*factorial(num-1);
//以下就实现了函数名的解耦合,在js中通常都是使用这种方式做递归
else return num*arguments.callee(num-1);
}
我们已经知道,设置类的属性和方法需要通过this关键字来引用;但是,this关键字在调用时会根据不同的调用对象变得不同。
【例】:
var color = "red";
function showColor() {
alert(this.color);
}
/*创建了一个类,有一个color的属性和一个show的方法*/
function Circle(color) {
this.color = color;
this.showColor = showColor;
}
var c = new Circle("yellow");
//使用c来调用showColor方法,等于调用了showColor()方法
//此时的this是c,所以color就是yellow
c.showColor(); //yellow
//此时调用的对象等于是window,showColor的this就是window,所以就会找window中color
showColor(); //red,html默认都有with(window);
函数有两个非常有用的属性:length、prototype
length: 是指该函数期望传进来的参数的个数(就是括号中的参数个数);
prototype: (后面讲)
函数还有两个比较有趣的方法:call、apply,他们都可以通过函数名称来调用函数。
l call:第一个参数是上下文,后面的参数是不同函数参数;
l apply:都有两个参数:第一个是调用的上下文、第二个是参数数组(可以将arguments穿进去)。
【提示】:
call和apply的区别就是第二个参数:
call(this,arg1,arg2,…),
apply(this,arguments)或者apply(this,[arg1, arg2, arg3])
【例】:
function sum(num1,num2) {
return num1+num2;
}
function callSum1(num1,num2) {
//使用sum这个函数来完成一次调用,调用的参数就是callSum1这个函数的参数
//apply的第二个参数表示一组参数数组
//这里的this代表window(表示window中的callSum1函数)
return sum.apply(this,arguments);
}
function callSum2(num1,num2) {
//关键就是第二个参数是数组
return sum.apply(this,[num1,num2]);
}
alert(callSum1(12,22));
alert(callSum2(22,32));
function callSum3(num1,num2) {
//call是通过参数列表来完成传递,其他和apply没有任何区别
return sum.call(this,num1,num2);
}
【例】:call的作用:
var color = "red";
function showColor() {
alert(this.color);
}
/*创建了一个类,有一个color的属性和一个show的方法*/
function Circle(color) {
this.color = color;
}
var c = new Circle("yellow");
showColor.call(this);//使用上下文来调用showColor,结果是red
showColor.call(c);//上下文对象是c,结果就是yellow
/*通过以上发现,使用call和apply之后,对象中可以不需要定义方法了,这就是call和apply的一种运用*/
【例】:
/*在js中并不存在类,所以可以直接通过Object来创建对象但是使用如下方式创建,带来最大的问题是,由于没有类的约束无法实现对象的重复利用,并且没有一种约定,在操作时会带来问题*/
var person = new Object();
person.name = "Leon";
person.age = 33;
person.say = function() {
alert(this.name+","+this.age);
}
【提示】:
l 除了上面注释中提到的问题外,若我们在网络传递对象的话,用上面的方法也是不可行的,在网络中传递数据的话,传递字符串是最好的。
l 如果在网络中传递对象我们可以将对象用xml的格式传递,但是用xml传递的话,会传多余的字符串,所以我们可以另外一种数据格式Json格式来传递对象。
l JavaScript Simple Object Notation
l Json就是js的对象,但是其省去了xml的标签,而是使用花括号(一个花括号就是一个对象):
/*json的意思就是javascript simple object notationjson就是js的对象,但是它省去了xml中标签,而是通过{}来完成对象的说明*/
var person = {
name:"张三",//通过属性名:属性值来表示,不同的属性通过,来间隔
age:23,
say:function() {
alert(this.name+","+this.age);
}//最后一个属性之后不能有逗号
}
person.say();
/**
* 通过json依然可以创建对象数组,创建的方式和js的数组一样
*/
var ps = [
{name:"Leon",age:22},
{name:"Ada",age:33}
];
for(var i=0;i<ps.length;i++) {
alert(ps[i].name);
}
/*创建一组用户,用户的属性有
* name:string,age:int,friends:array
* List<Person> ps = new ArrayList<Person>();
* ps.add(new Person("Leon",22,["Ada","Alice"]));
* ps.add(new Person("John",33,["Ada","Chris"]));
* 把ps转换为json
*/
ps = [
{
name:"Leon",
age:22,
friends:["Ada","Alice"]
},
{
name:"John",
age:33,
friends:["Ada","Chris"]
}
];
【例】:
/*通过工厂的方式来创建Person对象在createPerson中创建一个对象然后为这个对象设置相应的属性和方法之后返回这个对象*/
function createPerson(name,age) {
var o = new Object();
o.name = name;
o.age = age;
o.say = function() {
alert(this.name+","+this.age);
}
return o;
}
/*使用工厂的方式,虽然有效的解决了类的问题,但是依然存在另外一个问题我们无法检测对象p1和p2的数据类型*/
var p1 = createPerson("Leon",22);
var p2 = createPerson("Ada",33);
p1.say();
p2.say();
【例】:
/*通过构造函数的方式创建,和基于工厂的创建类似最大的区别就是函数的名称就是类的名称,按照java的约定,第一个字母大写。使用构造函数创建时,在函数内部是通过this关键字来完成属性的定义*/
function Person(name,age) {
this.name = name;
this.age = age;
//以下方式带来的问题是所有的对象都会为该行为分配空间
// this.say = function() {
// alert(this.name+","+this.age);
// }
this.say = say;
}
/*将行为设置为全局的行为,如果将所有的方法都设计为全局函数的时候,这个函数就可以被window调用,此时就破坏对象的封装性而且如果某个对象有大量的方法,就会导致整个代码中充斥着大量的全局函数,这样将不利于开发*/
function say() {
alert(this.name+","+this.age);
}
/*通过new Person来创建对象*/
var p1 = new Person("Leon",22);
var p2 = new Person("Ada",32);
p1.say(); p2.say();
/*使用构造函数的方式可以通过以下方式来检测对象的类型*/
alert(p1 instanceof Person);
/**
* 使用构造函数创建所带来的第一个问题就是每一个对象中
* 都会存在一个方法的拷贝,如果对象的行为很多的话
* 空间的占用率就会大大增加
* 可以将函数放到全局变量中定义,这样可以让类中的行为指向
* 同一个函数
*/
alert(p1.say==p2.say);
【提示】:
java中,类的函数是在栈中分配的,调用的时候才分配空间。
js中的难点:函数、Prototype、闭包。
【例】:
/*以下演示了通过原型的创建方式,使用基于原型的创建可以将属性和方法设置为Person专有的,不能再通过window来调用*/
function Person(){ }
Person.prototype.name = "Leon";
Person.prototype.age = 23;
Person.prototype.say = function() {
alert(this.name+","+this.age);
}
var p1 = new Person();
p1.say();
//通过window没有办法调用say方法,如此就完成了封装
// say();
//第一种状态:新建对象
function Person(){
}
这个执行完后,内存中分配了两块空间:
Person对象和Person的原型对象(Person prototype)
l 原型对象中的construct属性指向了Person函数(这就是为什么可以使用new Person())
l Person的prototype属性指向了Person的原型对象
//第二种状态:为原型设置属性值
Person.prototype.name = "Leon";
Person.prototype.age = 23;
Person.prototype.say = function() {
alert(this.name+","+this.age);
}
以上那些属性都是保存在Person的原型对象中。
//第三中状态:创建对象的实例:
//创建了一个对象之后,对象中会有一个_prop_属性指向原型(对象)
//在使用时如果在对象内部没有找到属性(比如p1.say())会去原型中找,完成say
//的调用,_prop_属性是隐藏的
var p1 = new Person();
//以下方法可以检测出p1是否有_prop_指向Person的原型
alert(Person.prototype.isPrototypeOf(p1));
//第四种状态:在自己的空间定义属性值
var p2 = new Person();
//在自己的空间中定义一个属性,此时,p2中也会有一个_prop_指向Person原型对象,
//定义的属性不会替换原型中的属性是在自己的空间存储了name=Ada,调用say方法
//查找name属性时,会先在自己空间查找name,找到后就不会到原型对象中查找了
p2.name = "Ada";//
//检测某个对象是否是某个函数的原型
alert(Person.prototype.isPrototypeOf(p2));
//检测某个对象的constructor是否指向Person
alert(p1.constructor==Person);
//检测某个属性是否是自己的属性
alert(p1.hasOwnProperty("name"));//false,p1自己的空间中没有值
alert(p2.hasOwnProperty("name"));//true,p2在自己的空间中设置了name属性
delete p2.name;
p2.say();
alert(p2.hasOwnProperty("name"));//由于已经删除了,所以是false
//检测某个对象在原型或者自己中是否包含有某个属性,通过in检测
alert("name" in p1);//true
alert("name" in p2);//true
alert("address" in p1);//在原型和自己的空间中都没有,false
alert(hasPrototypeProperty(p1,"name"));//true
alert(hasPrototypeProperty(p2,"name"));//false
/*可以通过如下方法检测某个属性是否在原型中存在*/
function hasPrototypeProperty(obj,prop) {
//不在自己的空间中但是存在于原型对象中
return ((!obj.hasOwnProperty(prop))&&(prop in obj))
}
l 在上一节“原型设计-基于模型”中,我们发现那种写法非常的麻烦,我们可以通过json格式来编写。
【例】:
/*使用如下方式来编写代码,当属性和方法特别多时,编写起来不是很方便,可以通过json的格式来编写*/
Person.prototype.name = "Leon";
Person.prototype.age = 23;
Person.prototype.say = function() {
alert(this.name+","+this.age);
}
/*以下方式将会重写原型由于原型重写,而且没有通过Person.prototype来指定此时的constructor不会再指向Person而是指向Object如果constructor真的比较重要,可以在json中说明原型的指向*/
Person.prototype = {
constructor:Person,//手动指定constructor
name:"Leon",
age:23,
say:function() {
alert(this.name+","+this.age);
}
}
var p1 = new Person();
p1.say();
alert(p1.constructor==Person); //手动指定constructor后输出true
【可能的问题】:
问题1:
//第一种状态
function Person(){
}
var p1 = new Person();
Person.prototype.sayHi = function() {
alert(this.name+":hi");
}
/*如果把重写放置在new Person之后,注意内存模型,见下图*/
Person.prototype = {
constructor:Person,//手动指定constructor
name:"Leon",
age:23,
say:function() {
alert(this.name+","+this.age);
}
}
p1.sayHi();//不会报错,但是没有this.name
var p2 = new Person();
p2.sayHi();//此时p2没有sayHi,所以就会报错
p2.say();
p1.say();
问题2:给实例添加一个属性时,可能会往原型中添加:
/*基于原型的创建虽然可以有效的完成封装,但是依然有一些问题
*1、无法通过构造函数来设置属性值
*2、当属性中有引用类型变量是,可能存在变量值重复
*/
function Person(){ }
Person.prototype = {
constructor:Person,
name:"Leon",
age:30,
friends:["Ada","Chris"],
say:function() {
alert(this.name+"["+this.friends+"]");
}
}
var p1 = new Person();
p1.name = "John";
p1.say();//john[ada,chris]
//会在原型中找friends,所以Mike是在原型中增加的
p1.friends.push("Mike");//为p1增加了一个朋友
var p2 = new Person();
//此时原型中就多了一个Mike,这就是原型所带来的第二个问题
p2.say();//leon[ada chris mike]
l 为了解决“问题1”、“问题2”,此处需要通过组合构造函数和原型来实现对象的创建:将属性在构造函数中定义,将方法在原型中定义,这中方式有效的结合了两者的优点,是目前常用的方式:
【问题解决方法】:
/*为了解决原型所带来的问题,此处需要通过组合构造函数和原型来实现对象的创建
*将属性在构造函数中定义,将方法在原型中定义
*这种有效集合了两者的优点,是目前最为常用的一种方式
*/
function Person(name,age,friends){
//属性在构造函数中定义
this.name = name;
this.age = age;
this.friends = friends;
}
Person.prototype = {
constructor:Person,
//方法在原型中定义
say:function() {
alert(this.name+"["+this.friends+"]");
}
}
//此时所有的属性都是保存在自己的空间中的(如果方法也定义在Person中)
//那么每个实例也都将有自己的方法拷贝
var p1 = new Person("Leon",23,["Ada","Chris"]);
p1.name = "John";
p1.friends.push("Mike");//为p1增加了一个朋友,但是不影响p2
p1.say();
var p2 = new Person("Ada",33,["Leon"]);
p2.say();
</script>
l 上面的“【问题解决方法】”中,是将方法定义在类的外面的,我们可以使用“动态原型”的方法,将方法放到类里面(这样就更接近Java的风格了)
/*为了让定义的方式更加符合java的需求,就把定义方法的原型代码放置到Person这个构造函数中*/
function Person(name,age,friends){
//属性在构造函数中定义
this.name = name;
this.age = age;
this.friends = friends;
//不能使用重写的方式定义(因为每次定义的时候都会执行重写)
Person.prototype = {
constructor:Person,
//方法在原型中定义
say:function() {
alert(this.name+"["+this.friends+"]");
}
}
/*而应该使用以下方法定义:
判断Person.prototype.say是否存在,如果不存在就表示需要创建
当存在之后就不会在创建了*/
if(!Person.prototype.say) {
Person.prototype.say = function() {
alert(this.name+"["+this.friends+"]");
}
}
}
//此时所以的属性都是保存在自己的空间中的
var p1 = new Person("Leon",23,["Ada","Chris"]);
p1.name = "John";
p1.friends.push("Mike");
p1.say();
var p2 = new Person("Ada",33,["Leon"]);
p2.say();