1.1 今日目标
- 能够理解什么是回调函数
- 能够理解事件对象的基本含义及用法
- 能够理解闭包的基本概念及作用
- 能够说出作用域的访问规则及原理
- 能够定义JS中的直接量形式的对象
- 能够定义js中的函数构造器
- 能够操作JS对象的成员属性和成员方法
- 能够使用构造函数定义类和对象
- 能够使用构造函数和原型链的混合方式定义类和对象
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>
运行结果
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>
运行结果
第二步:封装函数
<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>
小结:获取原型的方法有两种
1、构造器的.prototype属性
2、对象的.__proto__属性
1.6 构造器(constructor)
每个原型都有一个constructor属性,指向构造函数
对象、构造器、原型的关系
<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>
练习一:画出Array的原型链
练习二:画出Math的原型链
Math不是构造函数,是对象
<script>
new Math(); //Uncaught TypeError: Math is not a constructor
</script>