1.1 今日目标

  1. 能够理解什么是回调函数
  2. 能够理解事件对象的基本含义及用法
  3. 能够理解闭包的基本概念及作用
  4. 能够说出作用域的访问规则及原理
  5. 能够定义JS中的直接量形式的对象
  6. 能够定义js中的函数构造器
  7. 能够操作JS对象的成员属性和成员方法
  8. 能够使用构造函数定义类和对象
  9. 能够使用构造函数和原型链的混合方式定义类和对象

1.2 作用域链

1.2.1 概述

多个函数中有多个作用域,多个作用域的集合称为作用域链

<script>
var num=10;
function fun1(){
	var num2=20;
	function fun2(){
		var num3=10;
		console.log(num3);     //10
	}
	fun2();
}
fun1();
</script>

注意:变量的查找,由内往外查找。

1.2.2 特性

1、内部环境可以访问外部环境的变量,外部的不能 访问内部的变量

2、变量的作用域是声明的时候决定的,与在什么地方调用无关

<script>
var num=10;
function fun1(){
	console.log('你的数字是:'+num);
}
function fun2(){
	var num=20;
	fun1();//你的数字是:10。fun1()不管在什么地方调用,结果都是10,与调用的地方无关。
}
fun2();		
fun1();	//你的数字是:10
</script>

1.2.3 变量访问的优先级

<script>
var num=10;    		//外部变量
function fun(num){	//形参
	var num=30;		//内部变量
	console.log(num);
	function num(){	//内部函数
	}
}
fun(20);
//内部变量>内部函数 >形参>外部变量

小结:先加载函数,后加载变量,所以同名的变量会覆盖同名的函数。

1.2.4 练习题

面试题一:

var num=10;
fun1();
function fun1(){
	console.log(num);	//undefined
	var num=20;	
	console.log(num);	//20
}
console.log(num);		//10

面试题二:

var num=10;
fun1();
function fun1(){
	console.log(num);	//10
	num=20;	
	console.log(num);	//20
}
console.log(num);		//20

面试题三:

var num=10;
function fun1(num){
	console.log(num);	//20
}
function fun2(){
	var num=20;
	fun1(num);
}
fun2(); 

面试题四:

var str='tom';
function fun() {
	console.log(str);	//undefined
	var str='berry';
}
fun();

面试题五:

if(!(a in window)){
	var a=1;
}
console.log(a);		//undefined
/*
全局变量都是window对象的属性,
分析:隐式声明了全局变量a,全局变量a属于window对象,所以不执行`var a=1`。所以输出的a是undefined。
*/

面试题六:

function fun() {
}
var fun;   //变量名和函数名同名,函数的地址自动保存到变量中
alert(fun);  //输出变量fun,变量fun中保存了函数fun的地址。

-----------------------------------------
    
function fun() {
}
var fun=2;	
alert(fun);	//2

1.3 闭包

1.3.1 概述

闭包:两个函数相互嵌套就形成了闭包。

形成闭包条件:

1、内部函数要访问外部函数的变量

2、内部函数需要通过return给返回出来.

<script>
function fun1(){
	var num=10;
	function fun2(){
		console.log(num);   //条件一:内部函数要访问外部的变量
	}
	return fun2;			//条件二:内部函数必须要return出来。
}
var str=fun1();
str();	//10
</script>

1.3.2 闭包的使用

使用一:闭包允许在全局的环境中访问局部变量,使得访问数据更加的安全

通全局变量统计函数调用的次数
<script>
var count=0;			//count的值可以被篡改,数据不安全
function fun(){
	count++;
	console.log('函数被调用了'+count+'次');
}
</script>
使用闭包统计
<script>
function outer(){
	var count=0;  //除了fun()可以更改count的值,没有其他办法可以修改count的值
	function fun(){
		count++;  
		console.log('函数被调用了'+count+'次');
	}
	return fun;
}
var str=outer();
</script>

使用二:每调用一次闭包,内存就划分一个空间。同一个闭包可以创建多个空间,每个闭包(空间)是独立的。

思考如果代码输出什么

<script>
var arr=new Array();
for(var i=0;i<=4;i++){
	arr[i]=function(){
		console.log(i);
	}
}
arr[0]();	//5    访问的是同一个i
arr[1]();	//5
arr[2]();	//5
</script>

思考如果代码输出什么

<script>
function fun1(num){
	function fun2(){
		console.log(num);
	}
	return fun2;
}

var arr=new Array();
for(var i=0;i<=4;i++){
	arr[i]=fun1(i);
}
arr[0]();	//0   不同的闭包函数中保存的不同的i
arr[1]();	//1
arr[2]();	//2
</script>

使用三:闭包可以使得局部变量一直驻留在内存中,不被销毁。

<script>
function initCard(name){
	var money=0;
	function saveMoney(num){
		money+=num;
		console.log(name+'你好:您存'+num+'元,余额是:'+money);
	}
	function getMoney(num){
		money-=num;
		console.log(name+'你好:您取'+num+'元,余额是:'+money);
	}
	return {
		saveMoney:saveMoney,
		getMoney:getMoney
	} 
}
//测试
var p1=initCard('tom');
p1.saveMoney(1000);		//tom你好:您存1000元,余额是:1000
p1.getMoney(100);		//tom你好:您取100元,余额是:900
</script>

小结:闭包可以在全局的环境下访问局部变量,每次调用闭包都会在内存划分空间,调用完毕后空间不销毁。

例题:

<script>
window.onload=function(){
	var aLi=document.getElementsByTagName('li');
	//方法一:通过this
	/*
	for(var i=0;i<aLi.length;i++){
		//鼠标移上去
		aLi[i].οnmοuseοver=function(){
			this.style.backgroundColor='#ccc';
		}
		//鼠标移走
		aLi[i].οnmοuseοut=function(){
			this.style.backgroundColor='#fff';
		}
	}
	*/
	//方法二:闭包
	for(var i=0;i<aLi.length;i++){
		aLi[i].onmouseover=setStyle(i,'#ccc');
		aLi[i].onmouseout=setStyle(i,'#fff');
	}

	function setStyle(i,color){
		function fun(){
			aLi[i].style.backgroundColor=color;
		}
		return fun;
	}
}
</script>
<ul>
	<li>锄禾日当午</li>
	<li>汗滴禾下土</li>
	<li>谁知盘中餐</li>
	<li>粒粒皆辛苦</li>
</ul>

运行结果

JavaScript高级(一)

1.3.3 闭包的内存管理

闭包占用的内存是不会释放的,如果滥用闭包,会造成内存泄漏

内存泄漏:程序己动态分配的内存未释放或无法释放,导致程序运行速度减慢甚至系统崩溃等后果。

1.4 面向对象

1.4.1 概述

面向过程:面向的是动作,站在执行者的角度

面向对象:面向实现动作的对象,站在指挥者的角度,对象的方法中封装的面向过程。

1.4.2 面向过程到面向对象演化

第一步:面向过程

<style>
	div{
		width: 200px; height: 50px; margin-bottom: 10px;
	}
</style>
<div></div>
<div></div>
<script>
    //第一步:获取节点
	var div=document.querySelectorAll('div');
    //第二步:给节点添加样式
	for(var i=0;i<div.length;i++){
		div[i].style.border='1px solid #d00';
	}
</script>

运行结果

JavaScript高级(一)

第二步:封装函数

<script>
    //第一步:获取节点
    function getElements(selector){
    	return document.querySelectorAll(selector);
    }
    //第二步:给节点添加样式
    function setBorder(elem){
    	for(var i=0;i<elem.length;i++){
			elem[i].style.border='1px solid #d00';
		}
    }
	//第三步:调用
	var elem=getElements('div');
	setBorder(elem);
</script>

第三步:封装对象

<script>
//封装对象
var obj={
	//第一步:获取节点
    getElements:function(selector){
    	return document.querySelectorAll(selector);
    },
    //第二步:给节点添加样式
    setBorder:function(elem){
    	for(var i=0;i<elem.length;i++){
			elem[i].style.border='1px solid #d00';
		}
    }
}    
//第三步:调用
var elem=obj.getElements('div');
obj.setBorder(elem);
</script>

1.5 创建对象

1.5.1 创建字面量对象

字面量对象就是字面本身,字面量对象里面都是键值对。

<script>
var obj={}	//声明字面量对象
console.log(obj);
//字面量对象里面都是键值对
var stu={name:'tom',sex:'男',exam:function(){alert('学生要考试')}};
console.log(stu.name);		//调用成员
console.log(stu['name']);	//调用成员
stu.exam();
//给字面量对象添加属性
stu.add='上海';
stu.eat=function(){
	console.log('学生要吃饭');
}
</script>

小结:

1、字面对象就是字面本身,用{}表示

2、字面量对象由键值对组成

3、调用成员方法: 对象.成员 对象[‘成员’]

4、可以给字面量对象添加成员

1.5.2 实例化Object创建对象

<script>
	//实例化对象
	var stu=new Object();
	console.log(stu);		//{}
	//给对象添加成员
	stu.name='tom';
	stu.eat=function(){
		console.log('学生要吃饭');
	}
	//调用成员
	console.log(stu.name);	//tom
	stu['eat']();		//学生要吃饭
</script>

注意:Object在JS中最顶层的父类。

1.5.3 工厂模式创建对象

批量创建对象

<script>
function createStu(name,sex){
	var stu={};
	stu.name=name;
	stu.sex=sex;
	return stu;
}
var stu1=createStu('tom','男');
var stu2=createStu('berry','女');
console.log(stu1);	//{name: "tom", sex: "男"}
console.log(stu2);	//{name: "berry", sex: "女"}
</script>

1.5.4 实例化构造器创建对象

在JS中没有类的概念,用构造器充当类的构造方法。

<script>
function Student(name,n){
	this.name=name;		//姓名,要实例化必须写this
	this.subject=n;		//考试科目
	this.exam=function(){
		console.log('学生要考试');
	}
}
//测试
var stu=new Student('tom',2);
console.log(stu);	//Student {name: "tom", subject: 2, exam: ƒ}
</script>

注意:构造函数和普通函数没有区别,如果是new那就是构造函数

提醒:每次实例化都会分配一个exam空间,这样是浪费空间的

<script>
function Student(){
	this.exam=function(){
		console.log('学生要考试');
	}
}
var stu1=new Student();
var stu2=new Student();
console.log(stu1.exam==stu2.exam);   //false
</script>

1.4 方法共享

方法一:

<script>
function Student(){
	Student.exam=function(){		//类似于添加静态方法,方法属于类的成员

	}
}
var stu1=new Student();
var stu2=new Student();
console.log(stu1.exam==stu2.exam);	//true
</script>

方法二:

<script>
function exam(){

}
function Student(){
	this.exam=exam;		//将函数exam的地址保存到属性中
}
var stu1=new Student();
var stu2=new Student();
console.log(stu1.exam==stu2.exam);	//true
</script>

上面两种方法可以解决内存浪费的问题,但会造成“全局污染”。

全局污染:A页面引入了B页面,如果两个页面中有同名的变量,两个变量相互影响称为全局污染。在开发中尽量少使用全局变量。全局变量容易造成全局污染。

方法三:

<script>
var obj={
	exam:function(){		//将全局的exam封装到对象中。
	}
}

function Student(){
	this.exam=obj.exam;		
}
var stu1=new Student();
var stu2=new Student();
console.log(stu1.exam==stu2.exam);	//true
</script>

脚下留心:以上三种方法也不是最好的方法,真正解决的方法是“原型”。

1.5 原型

1.5.1 prototype

每个函数都有一个prototype属性,prototype属性中保存了一个对象,这个对象就称为原型。

<script>
function Student(){
}
console.log(Student.prototype);    //{constructor: ƒ}
</script>

原型作用:只要是通过构造函数创建的对象都可以访问原型上的成员。

例题

<script>
function Student(){
}
Student.prototype.name='tom';		//给原型对象添加name属性
Student.prototype.exam=function(){	//给原型对象添加exam方法,可以防止全局污染
	console.log('学生要考试');
}
//console.dir(Student.prototype);	//console.dir()打印对象上所有的成员
var stu1=new Student();
var stu2=new Student();
console.log(stu1.name,stu2.name);
console.log(stu1.exam==stu2.exam);		//true
</script>

多学一招:对象的内置方法都存在原型上

<script>
console.log([]==[]);			//false
console.log([].push==[].push);	//true
console.log(Array.prototype);	//数组的内置方法都保存在Array的原型中
</script>

1.5.2 __proto__

任何对象都有__proto__属性

对象的__proto__属性保存的构造函数的原型

<script>
function Student(){
}
var stu=new Student();
console.log(Student.prototype==stu.__proto__);	//true
</script>

JavaScript高级(一)

小结:获取原型的方法有两种

1、构造器的.prototype属性

2、对象的.__proto__属性

1.6 构造器(constructor)

每个原型都有一个constructor属性,指向构造函数

对象、构造器、原型的关系

JavaScript高级(一)

<script>
function Student(){
}
var stu=new Student();
console.log(stu.__proto__.constructor==Student);	//true
console.log(Student.prototype.constructor)			//Student
</script>

1.7 原型链

每个对象都有__proto__属性指向它的原型对象,原型对象也有__proto__属性指向了自己的原型对象,形成一个链式结构称为原型链

<script>
function Student(){
}
console.log(Student.prototype);						// ƒ Student()
console.log(Student.prototype.__proto__.constructor);	//ƒ Object()
console.log(Student.prototype.__proto__);		//ƒ Object()
console.log(Student.prototype.__proto__.__proto__);		//null
</script>

JavaScript高级(一)

练习一:画出Array的原型链

JavaScript高级(一)

练习二:画出Math的原型链

Math不是构造函数,是对象

<script>
new Math();		//Uncaught TypeError: Math is not a constructor
</script>

JavaScript高级(一)

相关文章: