在js中,函数嵌套是非常普遍的。
在函数嵌套中,对变量是如何查找的?
- 首先在函数内寻找,寻找不到,则往外层寻找...直到全局(window)区域
声明变量 var,用来定义变量。
注:以window.xxx引用全局变量,寻找不到,作为某个属性不存在,返回undefined;直接以xxx引用某变量,寻找不到,报xxx is not defined,错误。如下所示:
console.log(window.d);//undefined
console.log(d);//d is not defined
js代码由上而下执行,但在执行前,都有个词法分析过程。
js代码整体执行过程分:
- 词法分析过程;
- 执行过程。
- 先分析参数
- 再分析变量声明
- 分析函数声明
一个函数能使用的局部变量,就从上面的3步分析而来。
具体步骤:
1、函数运行前的一瞬间,生成 Active Object(活动对象)
2、把声明的参数,形成AO的属性,值全是undefined;接收实参,形成AO相应的属性值
3、分析变量声明,如var age
如果AO上已经有age属性,则不做任何影响;
如果AO上没有age属性,则添加AO属性,值是undefined
4、分析函数声明,如function t(){}.
则把函数赋给AO.t属性
注:如果此前t属性已存在,则被无情覆盖
function t(age){
alert(age);
}
t(5);//5
t();//undefined
词法分析过程:
AO{age:undefined}
运行过程:
t(5)->AO.age = 5,alert(AO.age);//5
t()->AO.age没得到赋值,还是undefined
function t2(age){
var age = 99;
alert(age);
}
t2(5);//99
分析过程
1、形成AO{}
2、分析形参,AO={age:undefined}
3、分析var age,发现AO已有age属性,不做任何影响
执行过程AO.age = 99;
alert(AO.age);
function t3(greet){
var greet = 'hello';
alert(greet);
function greet(){}
alert(greet);
}
t3(null);//hello hello
function t(a){
alert(a);
var a = 2;
alert(a);
a = function(){
alert(a);
return 4;
};
alert(a());
}
t(22);
Arguments
Arguments,是个对象,很像数组的对象。
Arguments,是函数运行时,实参列表。
Arguments,收集所有的实参,即使没有与之对应的形参。
(function(a,b,c){
console.log(typeof arguments);//object
console.log(arguments);//["hello", "world", "!", " everyone"]
console.log(arguments[0]);//hello
})("hello","world","!"," everyone");
形参与对应的Arguments元素,其实是相互映射,互相影响的。
(function(a,b,c){
console.log(arguments);//["hello", "world", "!", " everyone"]
console.log(arguments[1]);//world
arguments[1] = "china";
console.log(b);//china
})("hello","world","!"," everyone");
Arguments.callee属性代表“当前运行的函数”。适用:匿名函数,立即执行,完成递归。
console.log((function(n){
//console.log(arguments.callee);
if(n == 1){
return n ;
}
else{
return (n+arguments.callee(n-1));
}
})(100));//5050
函数运行期内,关键的三个对象:
AO 本函数AO上没有其属性,则继续去外层函数上找,直到全局对象,叫做 作用域链
Arguments 每个函数有自己的callee,但不向外层接着找arguments的相关属性,即 不形成链
this 对象本身
thisjs中函数调用this的4种方式:
作为普通函数来调用时,this的值指向window准确的说,this为null,但被解释成window
在ECMASCRIPT5标准中,如果this为null,则解释成undefined。
console.log(window.x);//undefined
function t(){
this.x = 210;
}
console.log(window.x);//210
作为对象的方法来调用
this指向方法的调用者,即该对象。
var obj = {x:99,y:11,t:function(){console.log(this.x)}};
obj.t();//99
var dog = {x:"wang"};
dog.t = obj.t;
dog.t();//wang
不管被调用函数,声明时属于方法还是函数。
show = function(){
alert("show "+this.x);
}
dog.t = show;
dog.t();//show wang
作为构造函数调用时
js中没有类的概念,创建对象是用构造函数来完成,或直接用json格式{}来写对象。
new Dog发生了以下几个步骤:
- 系统创建空对象{},(空对象constructor属性指向Dog函数);
- 把函数的this指向该空对象,空对象变成{name:name,age:age}
- 执行该函数
- 返回该对象
function Dog(name,age){
this.name = name;
this.age = age;
this.bark = function(){
console.log(this.name + " bark");
}
}
var dog = new Dog("Wang",2);
console.log(dog);//Dog {name: "Wang", age: 2}
dog.bark();//Wang bark
function Pig(){
this.age = 5;
return "abc";
}
var pig = new Pig();//new Pig返回的还是Pig对象,因为函数作为构造函数运行时,return后的语句就是无效的。
console.log(pig);//Pig {age: 5}
函数使用call、apply调用
函数.call(对象,参数1,参数2,...,参数n);
var human = {name:"yff",age:28}
function t(more){
console.log(this.name+";"+(this.age+more));
}
t.call(human,2);//yff;30
b包
function t1(){
var age = 20;
function t2(){
alert(age);
}
return t2;
}
var tmp = t1();
var age = 99;
tmp();//20
在js中,t1执行过程中,又生成了t2,而从作用于上来说,t2能访问到age = 20,于是“age = 20”没有消失,而是于返回的t1函数形成了一个“环境包”,这个包属于t2,所以叫闭包。
返回的t2是有自己的生态环境。
在js中,age = 20这个变量,却被t2捕捉,即使t1执行完毕,通过t2,依然能访问该变量。返回的函数并非孤立的函数,甚至把其周围的变量环境形成了一个封闭的“环境包”,所以叫闭包。
一句话概括:函数的作用域取决于声明时,而不取决于调用时。
js对象的特点在js中,对象不依赖于类而存在。
js的对象是一个“属性字典”,因此可以直接造对象。
var obj = {};//地球上最原始的蛋白质
var cell = {cell:1};//单细胞
var chicken = {head:1,leg:2,sing:function(){alert("sing sing")}};
{key:value}这种格式声明的对象,称为json格式的对象。
js的对象属性,是可以任意添加和删除的:
chicken.wine = 2;
delete chicken.wine;//删除chiecken的wine属性。
js中的对象,就是“一组属性与值的集合”,属性可以任意增减,方法和属性不必区分。
js的私有属性、封装用闭包来完成js面向对象之私有属性。
function Girl(name,bf){
var secret = bf;//私有属性,通过对象.secret是访问不了的。
this.name = name;
this.showLover = function(){//利用闭包封装,访问私有属性值。
return secret;
}
this.moveLover = function(abf){//利用闭包封装,修改私有属性值。
secret = abf;
}
}
var girl = new Girl("黛玉","宝玉");
alert(girl.name+"喜欢"+girl.showLover());
girl.moveLover("方世玉");
alert(girl.name+"移情别恋"+girl.showLover());
js的继承
js的继承,不是通过类的继承来实现的,而是通过“原型”概念来完成的。
function Tiger(){
this.bark = function(){//利用闭包封装
alert("我是百兽之王");
}
}
var heiTiger = new Tiger();
function Cat(){
this.climb = function (){
alert("我会爬树!");
}
}
var bosiCat = new Cat();
bosiCat.climb();//我会爬树!
我们明确的对Tiger函数指定,用某个具体的Cat对象做Tiger的原型,并创建Tiger对象。
实现继承:
Tiger.prototype = new Cat();
var huananTiger = new Tiger();
huananTiger.climb();//我会爬树!
在这个过程,发生了什么?
- 构造空对象huananTiger{};
- huananTiger.bark = function(){};
- huananTiger.proto = Tiger.prototype(即Cat对象) 这是继承关键。
js中,每个对象都有一个proto指向原型对象,原型对象也是对象,也有proto。
Tiger对象先在自身上寻找,没有climb()方法,去找原型,在原型Cat对象上找到climb()方法。
事件绑定function t(){
alert('click you!');
}
<a href="#" onclick="t()">点击</a>
分析:
- DOM对象的句柄;
- 句柄上绑定的函数;
- 事件发生的那一瞬间,关于事件的各种信息,如时间、发生时鼠标在屏幕上的坐标、事件类型等等。
这些信息被打包成一个对象,便于获取。
把事件写在标签的属性里把事件写在标签的属性里,如:<a href="#" onclick="t()" style="...">点击</a>
这是DOM 0级的标准(非常古老的标准)
好处:几乎所有的浏览器都支持
坏处:夹杂在html代码里,不简洁;事件写法,效率不高;不符合“行为、结构、样式相分离”
用事件属性来绑定事件函数window.onload = function(){
// 第一个好处:完成了行为的分离
document.getElementById("test1").onclick = function(){
alert('click you!');
}
// 第二个好处:便于操作当事对象,function是作为对象的on***属性出现的
// 因此,函数里的操作对象,直接用this就能引用当事对象。
document.getElementById("test2").onclick = function(){
this.style.background = "red";
}
// 第三个好处:可以方便读取事件对象
// 在事件触发时,系统自动把事件对象传递给事件函数,以其第一个参数来传
document.getElementById("test3").onclick = function(ev){
console.log(ev);
var test4 = document.getElementById("test4");
test4.style.left = ev.clientX;
test4.style.top = ev.clientY;
}
}
addEventListener
W3C中的标准:addEventListener
- 绑定在哪个事件上?click、change、load、focus、blur、mouseover...等等
- 绑定什么函数?自定义函数
- 什么方式监听执行事件函数?捕捉-true、冒泡-false,不填默认为false,建议填写。
var domobj = document.getElementById("xxx");
dombj.addEventListener("click",funciton(){
alert(1);
},false);
dombj.addEventListener("click",funciton(){
alert(2);
},false);
dombj.addEventListener("click",funciton(){
alert(3);
},false);
注:
- 事件名一律不带on;
- 绑定事件函数中的this指绑定该事件的对象;
- 执行顺序按照绑定的顺序执行。
捕捉模型、冒泡模型
向内聚焦(外->内)->捕捉模型
向外扩散(内->外)->冒泡模型
<!doctype html>
<html>
<head>
<style script="text/css">
#china{
width:300px;
height:300px;
border:1px solid red;
margin:auto;
}
#beijing{
width:200px;
height:200px;
border:1px solid blue;
margin:auto;
}
#haiding{
width:100px;
height:100px;
border:1px solid green;
margin:auto;
}
</style>
</head>
<body>
<div id="china">
<div id="beijing">
<div id="haiding">
</div>
</div>
</div>
</body>
<script>
function $(id){
return document.getElementById(id);
}
// 捕捉
$("china").addEventListener("click",function(){alert("进入 China")},true);
$("beijing").addEventListener("click",function(){alert("进入 beijing")},true);
$("haiding").addEventListener("click",function(){alert("进入haiding")},true);
//冒泡
$("china").addEventListener("click",function(){alert("离开 China")},false);
$("beijing").addEventListener("click",function(){alert("离开 beijing")},false);
$("haiding").addEventListener("click",function(){alert("离开 haiding")},false);
// 按照该顺序,正常顺序执行;
//若将冒泡与捕捉对调,不按照顺序正常执行
</script>
</html>
阻止事件
W3C标准:
- 阻止事件传播:event.stopPropagation();
- 阻止事件效果:eventPreventDefault();
// 修改捕捉里beijing绑定的事件 $("beijing").addEventListener("click",function(event){ alert("进入 beijing"); event.stopPropagation(); //不再执行下面绑定的事件 },true);
IE8及以下:
- event.cancelBundle();
- event.returnValue=false;
解除绑定
- obj.removeEventListener("事件",监听函数名,true/false);
IE8及以下:
- obj.detachEvent("on事件",监听函数名,true/false)
Javascript语言的面向对象特征很弱,其他面向对象语言在创建类时只要使用关键字static即可指定类为静态类,Javascript没有提供static这样的关键字,要让Javascript也具有“静态”特性只有靠一些技巧了。
代码中列举了两种静态方法/属性的实现方式:
- 静态类的静态方法和属性;
- 非静态类的静态方法和属性。
/****************************************
* 方法一
* 类、方法、属性都为静态类型
* 不能创建实例
*****************************************/
var Time = {
today: '2009-3-8',
weather: 'rain',
show: function() {
alert('Today is ' + this.today);
}
};
//静态对象可直接使用,无需创建实例
alert('It is ' + Time.weather + ' today.');
Time.show();
//下面的代码会出错,因为静态类不能创建实例
//var t = new Time();
//t.show();
普通对象,同时拥有静态和非静态属性、方法
/****************************************
* 方法二
* 普通对象,同时拥有静态和非静态属性、方法
* 可以用实例化
* 注意:
* 1.静态方法/属性使用类名访问
* 2.非静态方法/属性使用实例名访问
*****************************************/
function Person(name) {
//非静态属性
this.name = name;
//非静态方法
this.show = function() {
alert(‘My name is ‘ + this.name + ‘.’);
}
}
//添加静态属性,人都是一张嘴
Person.mouth = 1;
//添加静态方法,哇哇大哭
Person.cry = function() {
alert(‘Wa wa wa …’);
};
//使用prototype关键字添加非静态属性,每个人的牙可能不一样多
Person.prototype.teeth = 32;
//非静态方法必须通过类的实例来访问
var me = new Person('Zhangsan');
//使用非静态方法、属性
me.show();
alert('I have ' + me.teeth + ' teeth.');
//使用静态方法、属性
Person.cry();
alert('I have ' + Person.mouth + ' mouth.');
对象、实例上添加方法、属性
- 利用prototype
prototype 属性使您有能力向对象添加属性和方法。
- 当构建一个属性,所有的对象将被设置属性,它是默认值。
- 在构建一个方法时,所有的对象都可以使用该方法。
var BaseClass = function() {};
//BaseClass的每个对象上的原型都有method1方法。
BaseClass.prototype.method1 = function(){
alert(' This is a instance method ');
}
var instance1 = new BaseClass();
//method1这个是BaseClass对象上的方法,所有BaseClass的实例都有该方法
instance1.method1();
- 实例上直接定义
var BaseClass = function() {};
var instance1 = new BaseClass();
instance1.method1 = function(){
alert(' This is a instance method too ');
}
//只有instance1这个实例对象上有method1方法。
instance1.method1();
- 构造函数里定义
//BaseClass的每个对象上都有method1方法。
var BaseClass = function() {
this.method1 = function(){
alert(' This is a instance method too ');
}
}
var instance1 = new BaseClass();
instance1.method1();
js里的方法介绍
apply、call
- apply:方法能劫持另外一个对象的方法,继承另外一个对象的属性.
Function.apply(obj,args)方法能接收两个参数
obj:这个对象将代替Function类里this对象
args:这个是数组,它将作为参数传给Function(args-->arguments)
<script type="text/javascript">
/*定义一个人类*/
function Person(name,age) {
this.name=name; this.age=age;
}
/*定义一个学生类*/
function Student(name,age,grade) {
Person.apply(this,arguments);
this.grade=grade;
}
//创建一个学生类
var student=new Student("qian",21,"一年级");
//测试
alert("name:"+student.name+"\n"+"age:"+student.age+"\n"+"grade:"+student.grade);
//name:qian age:21 grade:一年级
//学生类里面没有给name和age属性赋值,为什么又存在这两个属性的值呢?
//这个就是apply的神奇之处.
//分析: Person.apply(this,arguments);
//this:在创建对象在这个时候代表的是student
//arguments:是一个数组,也就是["qian","21","一年级"];
//也就是通俗一点讲就是:用student去执行Person这个类里面的内容
//在Person这个类里面存在this.name等之类的语句,这样就将属性创建到了student对象里面
</script>
- call:和apply的意思一样,只不过是参数列表不一样.
Function.call(obj,[param1[,param2[,…[,paramN]]]])
obj:这个对象将代替Function类里this对象
params:这个是一个参数列表
//call例子:将apply里的例子里,apply换成call,就可以了。
- 什么情况下用apply,什么情况下用call
在给对象参数的情况下,如果参数的形式是数组的时候,比如apply示例里面传递了参数arguments,这个参数是数组类型,并且在调用Person的时候参数的列表是对应一致的(也就是Person和Student的参数列表前两位是一致的) 就可以采用 apply , 如果我的Person的参数列表是这样的(age,name),而Student的参数列表是(name,age,grade),这样就可以用call来实现了,也就是直接指定参数列表对应值的位置(Person.call(this,age,name,grade));
- apply小技巧(参数列表改成数组传值)
Math.max(param1,param2,param3…) ->var max=Math.max.apply(null,[param1,param2,param3…])
Math.min(param1,param2,param3…) ->var max=Math.min.apply(null,[param1,param2,param3…])
array.push(param1,param2,param3…) ->Array.prototype.push.apply(array,[param1,param2,param3…]);
setTimeoutsettimeout()方法要调用带参数的函数有两种方法:
- setTimeout(匿名函数,秒数)
var a=3;
setTimeout(function(){console.log(a)},1000);//1秒后执行,输出3
- setTimeout(字符串,秒数)
function test(a){
console.log(a);
}
//这里的参数只能是字符串形式的,而不能传递一个对象
setTimeout("test("+3+")",1000);//1秒后执行,输出3
var b = "abc5";
// 注意双引号内有单引号
setTimeout("test('"+b+"')",2000);//2秒后执行,输出abc5,