原型与原型链
函数的prototype
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.函数的prototype属性
每个函数都有一个prototype属性,它默认指向一个Object空对象(即称为:原型对象)
原型对象中有一个属性constructor,它指向函数对象
2.给原型对象添加属性(一般都是方法)
作用:函数的所有实例对象自动拥有原型中的属性(方法)
-->
<script type="text/javascript">
//每个函数都有一个prototype属性,它默认指向一个Object空对象(即称为:原型对象)
console.log(Date.prototype,typeof Date.prototype)
function Fun(){//alt+shift+r(重命名rename)
}
Fun.prototype.test=function (){
console.log('test()')
}
console.log(Fun.prototype)//默认指向一个Object空对象(没有我们的属性)
//原型对象中有一个属性constructor,它指向函数对象
console.log(Date.prototype.constructor===Date)
console.log(Fun.prototype.constructor===Fun)
//给原型对象添加属性(一般是方法)==>实例对象可以访问
Fun.prototype.test=function (){
console.log('test()')
}
var fun=new Fun()
fun.test()
</script>
</body>
</html>
显式原型与隐式原型
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.每个函数function都有一个prototype,即显式原型(属性)
2.每个实例对象都有一个_proto_,可称为隐式原型(属性)
3.对象的隐式原型的值为其对应构造函数的显式原型的值
4.内存结构
5.总结:
-函数的prototype属性:在定义函数时自动添加的,默认值是一个object对象
-对象的_proto_属性:创建对象时自动添加的,默认值为构造函数的prototype属性值
-程序员能直接操作显式原型,但不能直接操作隐式原型(ES6之前)
-->
<script type="text/javascript">
function Fn(){//内部语句:this.prototype={}
}
//1.每个函数function都有一个prototype,即显式原型(属性)
console.log(Fn.prototype)
//2.每个实例对象都有一个_proto_,可称为隐式原型(属性)
var fn=new Fn()//内部语句:this._proto_=Fn.prototype
console.log(fn.__proto__)
//3.对象的隐式原型的值为其对应构造函数的显式原型的值
console.log(Fn.prototype===fn.__proto__)//true
//给原型添加方法
Fn.prototype.test=function (){
console.log('test()')
}
//通过实例对象调用原型的方法
fn.test()
</script>
</body>
</html>
原型链
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.原型链
-访问一个对象的属性时,
先在自身属性中查找,找到返回
如果没有,再沿着__proto__这条链向上查找,找到返回
如果最终没找到,返回undefined
-别名:隐式原型链
-作用:查找对象的属性(方法)
2.构造函数/原型/实体对象的关系
3.构造函数/原型/实体对象的关系2
-->
<script type="text/javascript">
function Fn(){
this.test1=function (){
console.log('test1()')
}
}
Fn.prototype.test2=function (){
console.log('test2()')
}
var fn=new Fn()
fn.test1()
fn.test2()
console.log(fn.toString())
console.log('test3()')
fn.test3()
/*
1.函数的显式原型指向的对象默认是空的object实例对象(但Object不满足)
*/
console.log(Fn.prototype instanceof Object)//true
console.log(Object.prototype instanceof Object)//false
console.log(Function.prototype instanceof Object)//true
/*
2.所有函数都是Function的实例(包括它本身)
*/
console.log(Function.__proto__===Function.prototype)
/*
3.Object的原型对象是原型链的尽头
*/
console.log(Object.prototype.__proto__)//null
</script>
</body>
</html>
原型链的属性问题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.读取对象的属性值时:会自动到原型链中查找
2.设置对象的属性值时:不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值
3.方法一般定义在原型中,属性一般通过构造函数定义在对象本身上
-->
<script type="text/javascript">
function Fn(){
}
Fn.prototype.a='xxx'
var fn1=new Fn()
console.log(fn1.a,fn1)
var fn2=new Fn()
fn2.a='yyy'
console.log(fn1.a,fn2.a,fn2)
function Person(name,age){
this.name=name
this.age=age
}
Person.prototype.setName=function (name){
this.name=name
}
var p1=new Person('Tom',12)
p1.setName('Bob')
console.log(p1)
var p2=new Person('Jack',12)
p1.setName('Cat')
console.log(p2)
console.log(p1.__proto__===p2.__proto__)//true
</script>
</body>
</html>
探索instanceof
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.instanceof是如何判断的?
-表达式:A instanceof B
-如果B函数的显式原型对象在A对象的原型链上,返回true,否则返回false
2.Function是通过new自己产生的实例
-->
<script type="text/javascript">
/*
案例1
*/
function Foo(){}
var f1=new Foo()
console.log(f1 instanceof Foo);//true
console.log(f1 instanceof Object);//true
/*
案例2
*/
console.log(Object instanceof Function)//true
console.log(Object instanceof Object)//true
console.log(Function instanceof Function)//true
console.log(Function instanceof Object)//true
function Foo(){}
console.log(Object instanceof Foo)//false
</script>
</body>
</html>
面试题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script type="text/javascript">
//测试题1
function A(){
}
A.prototype.n=1
var b=new A()
A.prototype={
n:2,
m:3
}
var c=new A()
console.log(b.n,b.m,c.n,c.m)
//测试题2
function F(){}
Object.prototype.a=function (){
console.log('a()')
}
Function.prototype.b=function (){
console.log('b()')
}
var f=new F()
f.a()
//f.b()
F.a()
F.b()
console.log(f)
console.log(Object.prototype)
console.log(Function.prototype)
</script>
</body>
</html>
执行上下文与执行上下文栈
变量提升与函数提升
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.变量声明提升
-通过var定义(声明)的变量,在定义语句之前就可以访问到
-值:undefined
2.函数声明提升
-通过function声明的函数,在之前就可以直接调用
-值:函数定义(对象)
3.问题:变量提升和函数提升是如何产生的?
-->
<script type="text/javascript">
/*
面试题:输出undefined
*/
var a=3
function fn(){
console.log(a)
var a=4
}
fn()
console.log(b)//undefined 变量提升
fn2()//可以调用 函数提升
fn3()//不能调用 变量提升
var b=3
function fn2(){
console.log('fn2()')
}
var fn3=function (){
console.log('fn3()')
}
</script>
</body>
</html>
执行上下文
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.代码分类(位置)
-全局代码
-函数(局部)代码
2.全局执行上下文
-在执行全局代码前将window确定为全局执行上下文
-对全局数据进行预处理
-var定义的全局变量==>undefined,添加为window的属性
-function声明的全局函数==>赋值(fun),添加为window的方法
-this==>赋值(window)
-开始执行全局代码
3.函数执行上下文
-在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象(虚拟的,存在于栈中)
-对局部数据进行处理
-形参变量==>赋值(实参)==>添加为执行上下文的属性
-arguments==>赋值(实参列表),添加为执行上下文的属性
-var定义的局部变量==>undefined,添加为执行上下文的属性
-function声明的函数==>赋值(fun),添加为执行上下文的方法
this==>赋值(调用函数的对象)
-开始执行函数体代码
-->
<script type="text/javascript">
console.log(a1,window.a1)
window.a2()
console.log(this)
var a1=3
function a2(){
console.log('a2()')
}
console.log(a1)
console.log('----------')
//函数执行上下文
function fn(a1){
console.log(a1)//2
console.log(a2)//undefined
a3()//a3()
console.log(this)//window
console.log(arguments)//伪数组(2,3)
var a2=3
function a3(){
console.log('a3()')
}
}
fn(2,3)
</script>
</body>
</html>
执行上下文栈
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2.在全局执行上下文(window)确定后,将其添加到栈中(压栈)
3.在函数执行上下文创建后,将其添加到栈中(压栈)
4.在当前函数执行完后,将栈顶的对象移除(出栈)
5.当所有的代码执行完后,栈中只剩下window
-->
<script type="text/javascript">
//1.进入全局执行上下文
var a=10
var bar=function (x){
var b=5
foo(x+b) //3.进入foo执行上下文
}
var foo=function (y){
var c=5
console.log(a+c+y)
}
//2.进入bar执行上下文
bar(10)
</script>
</body>
</html>
执行上下文栈2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.依次输出什么?
-gb:undefined
-fb:1
-fb:2
-fb:3
-fe:3
-fe:2
-ge:1
2.整个过程中产生了几个执行上下文? 5
-->
<script type="text/javascript">
console.log('gb:'+i)
var i=1
foo(1)
function foo(i){
if(i==4){
return
}
console.log('fb:'+i)
foo(i+1)//递归调用:在函数内部调用自己
console.log('fe:'+i)
}
console.log('ge:'+i)
</script>
</body>
</html>
面试题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script type="text/javascript">
/*
测试题1:先执行变量提升,再执行函数提升
*/
function a(){}
var a;
console.log(typeof a)//‘function'
/*
测试题2:
*/
if(!(b in window)){
var b=1;
}
console.log(b)//'undefined'
/*
测试3:
*/
var c=1
function c(c){
console.log(c)
}
c(2)//报错
</script>
</body>
</html>
作用域与作用域链
作用域
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.理解
-就是一块"地盘",一个代码段所在的区域
-它是静态的(相对于上下文对象),在编写代码时就确定了
2.分类
-全局作用域
-函数作用域
-没有块作用域(ES6有)
3.作用
-隔离变量,不同作用域下同名变量不会有冲突
4.区别1
-全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时
-全局执行上下文环境是在全局作用域确定之后,js代码马上执行之前创建
-函数执行上下文是在调用函数时,函数体代码执行之前创建
5.区别2
-作用域是静态的,只要函数定义好了就一直存在,且不会再变化
-执行上下文是动态的,调用函数时创建,函数调用结束时就会自动释放
6.联系
-执行上下文(对象)是从属于所在的作用域
-全局上下文环境==>全局作用域
-函数上下文环境==>对应的函数使用域
-->
<script type="text/javascript">
var a=10,
b=20
function fn(x){
var a=100,
c=300;
console.log('fn()',a,b,c,x)
function bar(x){
var a=1000,
d=400
console.log('bar()',a,b,c,d,x)
}
bar(100)
bar(200)
}
fn(10)
</script>
</body>
</html>
作用域链
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.理解
-多个上下级关系的作用域形成的链,它的方向是从下向上的(从内到外)
-查找变量时就是沿着作用域链来查找到
2.查找一个变量的查找规则
-在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入2
-在上一级作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入3
-再次执行2的相同操作,直到全局作用域,如果还找不到就抛出找不到的异常
-->
<script type="text/javascript">
var a=1
function fn1(){
var b=2
function fn2(){
var c=3
console.log(c)
console.log(b)
console.log(a)
console.log(d)
}
fn(2)
}
fn1(0)
</script>
</body>
</html>
面试题1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script type="text/javascript">
var x=10;
function fn(){
console.log(x);
}
function show(f){
var x=20;
f()
}
show(fn);
</script>
</body>
</html>
面试题2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script type="text/javascript">
var fn=function (){
console.log(fn)
}
fn()
var obj={
fn2:function (){
console.log(fn2)
}
}
obj.fn2()
</script>
</body>
</html>
闭包
引入
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<!--
需求:点击某个按钮,提示”点击的是第n个按钮“
-->
<script type="text/javascript">
var btns=document.getElementsByTagName('button')
//遍历加监听
for(var i=0,length=btns.length;i<length;i++){
var obj=btns[i]
//将btn所对应的下标保存在btn上
btn.index=i
btn.onclick=function (){
alert('第'+(this.index+1)+'个')
}
}
</script>
</body>
</html>
闭包的理解
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.如何产生闭包?
-当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包
2.闭包到底是什么?
-使用chrome调试查看
-理解一:闭包是嵌套的内部函数(绝大部分人)
-理解二:包含被引用变量(函数)的对象(极少数人)
-注意:闭包存在于嵌套的内部函数中
3.产生闭包的条件?
-函数嵌套
-内部函数引用了外部函数的数据(变量/函数)
-->
<script type="text/javascript">
function fn1(){
var a=2
var b='abc'
function fn2(){//执行函数定义就会产生闭包(不用调用内部函数)
console.log(a)
}
}
fn1()
</script>
</body>
</html>
常见的闭包
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.将函数作为另一个函数的返回值
2.将函数作为实参传递给另一个函数调用
-->
<script type="text/javascript">
//1.将函数作为另一个函数的返回值
function fn1(){
var a=2
function fn2(){
a++
console.log(a)
}
return fn2
}
var f=fn1()
f()//3
f()//4
//2.将函数作为实参传递给另一个函数调用
function showDelay(msg,time){
setTimeout(function (){
alert(msg)
},time)
}
showDelay('atguigu',2000)
</script>
</body>
</html>
闭包的作用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)
2.让函数外部可以操作(读写)到函数内部的数据(变量/函数)
问题:
-函数执行完后,函数内部生命的局部变量是否还存在? 一般情况下不存在,存在于闭包中的变量才可能存在
-在函数外部能直接访问函数内部的局部变量吗? 不能,但我们可以通过闭包让外部操作它
-->
<script type="text/javascript">
function fn1(){
var a=2
function fn2(){
a++
console.log(a)
}
function fn3(){
a--
console.log(a)
}
return fn3
}
var f=fn1()
f()//1
f()//0
</script>
</body>
</html>
闭包的生命周期
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.产生:在嵌套内部函数定义执行完时就产生了(不是在调用)
2.死亡:在嵌套的内部函数成为垃圾对象时
-->
<script type="text/javascript">
function fn1(){
//此时已经产生闭包(函数提升,内部函数对象已经创建了)
var a=2
function fn2(){
a++
console.log(a)
}
return fn2
}
var f=fn1()
f()//3
f()//4
f=null//闭包死亡(包含闭包的函数对象成为垃圾对象)
</script>
</body>
</html>
闭包应用-自定义JS模块
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>闭包</title>
</head>
<body>
<!--
闭包的应用:定义JS模块
-具有特定功能的js文件
-将所有的数据和功能都封装在一个函数内部(私有的)
-只向外暴露一个包含n个方法的对象或函数
-模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能
-->
<script type="text/javascript" src="myModule.js">
</script>
<script type="text/javascript">
var module= myModule()
module.doSomething()
module.doOtherthing()
</script>
</body>
</html>
闭包的缺点及解决
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.缺点
-函数执行完后,函数内的局部变量没有释放,占用内存时间会变长
-容易造成内存泄漏
2.解决
-能不用闭包就不用
-及时释放
-->
<script type="text/javascript">
function fn1(){
var arr=new Array[100000]
function fn2(){
console.log(arr.length)
}
return fn2
}
var f=fn1()
f()
f=null//让内部函数成为垃圾对象-->回收闭包
</script>
</body>
</html>
内存溢出与内存泄漏
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1.内存溢出
-一种程序运行出现的错误
-当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出的错误
2.内存泄漏
-占用的内存没有及时释放
-内存泄漏积累多了就容易导致内存溢出
-常见的内存泄露
-意外的全局变量
-没有及时清理的计时器或回调函数
-闭包
-->
<script type="text/javascript">
//1.内存溢出
var obj={}
for(var i=0;i<10000;i++){
obj[i]=new Array(1000000000)
console.log('-----')
}
//2.内存泄露
//意外的全局变量
function fn(){
a=3
console.log(a)
}
fn()
var intervalId=setInterval(function (){//启动循环定时器后不清理
console.log('----')
},1000)
//clearInterval(intervalId)
//闭包
function fn1(){
var a=4
function fn2(){
console.log(++a)
}
return fn2
}
var f=fn1()
f()
//f=null
</script>
</body>
</html>
面试题1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script type="text/javascript">
//代码片段1
var name="The window";
var object={
name:"My Object",
getNameFunc:function (){
return function (){
return this.name;
};
}
};
alert(object.getNameFunc()()); //? the window
//代码片段2
var name2="The window";
var object2={
name:"My Object",
getNameFunc:function (){
var that=this;
return function (){
return that.name;
};
}
};
alert(object2.getNameFunc()());//My Object
</script>
</body>
</html>
面试题2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script type="text/javascript">
function fun(n,o){
console.log(o)
return{
fun:function (m){
return fun(m,n);
}
};
}
var a=fun(0);a.fun(1);a.fun(2);a.fun(3);//undefined,0,0,0
var b=fun(0);fun(1);fun(2);fun(3);//undefined,0,1,2
var c=fun(0);fun(1);c.fun(2);c.fun(3);//undefined,0,1,1
</script>
</body>
</html>
Q.E.D.