前言
js中的原型毫无疑问一个难点,学习如果不掌握原理就容易晕!可能这时候懂,等几个小时过后再来写就很容易蒙,任何一个js知识点,比如学习事件流,闭包,继承等,对于这些知识点我们都应该先熟练原理,然后自己整理一套属于自己的理解说辞,才不会忘。
以下是我学习JavaScript高级原型链以及闭包的知识笔记
一、函数原型与原型链
一.原型
1.原型prototype属性
函数的prototype属性(图)
- 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
代码演示:
例一:console.log(Date.prototype, typeofDate.prototype) 输出的结果: Object {constructor: Function, toString: Function, toDateString: Function, toTimeString: Function, toISOString: Function, } object例二:functionfn() { } console.log(fn.prototype, typeoffn.prototype) 输出的结果:Object {constructor: Function} object
原型对象中有一个属性constructor, 它指向函数对象
代码演示:
console.log(Date.prototype.constructor===Date) console.log(fn.prototype.constructor===fn) *给原型对象添加属性(一般都是方法) >代码演示```javaScriptfunction F() {}F.prototype.age = 12 //添加属性F.prototype.setAge = function (age) { // 添加方法this.age = age}// 创建函数的实例对象var f = new F()console.log(f.age) //12f.setAge(23)console.log(f.age) //23
2.显示原型与隐式原型
每个函数function都有一个prototype,即显式原型
每个实例对象都有一个__proto__,可称为隐式原型
对象的隐式原型的值为其对应构造函数的显式原型的值
内存结构(图)
总结:
函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
代码举例:
例子原型链图示:
二.原型链
原型链
- 访问一个对象的属性时,
- 先在自身属性中查找,找到返回
- 如果没有, 再沿着__proto__这条链向上查找, 找到返回
- 如果最终没找到, 返回undefined
- 别名: 隐式原型链
- 作用: 查找对象的属性(方法)
代码演示:
functionFn() { this.test1=function () { console.log('test1()') //输出1 } } Fn.prototype.test2=function () { console.log('test2()') //输出2 } varfn=newFn() fn.test1() fn.test2() console.log(fn.toString()) //输出3fn.test3() //输出
例子原型链图示:
1.原型链的属性问题
- 读取对象的属性值时: 会自动到原型链中查找
- 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
- 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
代码演示:
functionPerson(name, age) { this.name=name; this.age=age; } Person.prototype.setName=function (name) { this.name=name; } Person.prototype.sex='男'; letp1=newPerson('Tom', 12) p1.setName('Jack') console.log(p1.name, p1.age, p1.sex) ///Jack 12 男p1.sex='女'console.log(p1.name, p1.age, p1.sex) ///Jack 12 女letp2=newPerson('Bob', 23) console.log(p2.name, p2.age, p2.sex) ///Bob 23 男
2.探究instanceof
- 表达式: A instanceof B
- 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
- Function是通过new自己产生的实例
代码演示:
//案例1functionFoo() { } varf1=newFoo(); console.log(f1instanceofFoo); //trueconsole.log(f1instanceofObject); //true//案例2console.log(ObjectinstanceofFunction) //trueconsole.log(ObjectinstanceofObject) //trueconsole.log(FunctioninstanceofObject) //trueconsole.log(FunctioninstanceofFunction) //truefunctionFoo() {} console.log(ObjectinstanceofFoo); //false
instanceof案例原型图
😀原型测试题
测试题
案例:
测试一: letA=function() { } A.prototype.n=1letb=newA() A.prototype= { n: 2, m: 3} letc=newA() console.log(b.n, b.m, c.n, c.m) //1 undefined 2 3
————————————————————————————————————————————测试二: letF=function(){}; Object.prototype.a=function(){ console.log('a()') }; Function.prototype.b=function(){ console.log('b()') }; letf=newF(); f.a() //a()f.b() //Uncaught TypeError: f.b is not a functionF.a() //a()F.b() //b()
图示输出结果:
测试一:
测试二:
二、执行上下文与执行上下文栈
一.变量的提升与函数提升
变量声明提升
- 通过var定义(声明)的变量, 在定义语句之前就可以访问到
- 值: undefined
- 函数声明提升
- 通过function声明的函数, 在之前就可以直接调用
- 值: 函数定义(对象)
- 问题: 变量提升和函数提升是如何产生的?
代码演示:
代码一: vara=4这里变量提升functionfn () { console.log(a) 可以访问a, 但值是undefinedvara=5这里变量提升} fn() 代码二: console.log(a1) 可以访问a1, 但值是undefineda2() vara1=3这里变量提升functiona2() { 这里函数提升console.log('a2()') }
图示
代码一图解:
代码二图解:
😀变量提升函数提升测试题
- 变量提升函数提升题目
代码演示:
varc=1变量提升functionc(c) { 函数提升console.log(c) 报错varc=3 } c(2)
输出结果
二.执行上下文
代码分类(位置)
全局代码
函数代码
全局执行上下文
在执行全局代码前将window确定为全局执行上下文
对全局数据进行预处理
var定义的全局变量==>undefined, 加为window的属性添
function声明的全局函数==>赋值(fun), 添加为window的方法
this==>赋值(window)
开始执行全局代码
函数执行上下文
在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象
对局部数据进行预处理
形参变量==>赋值(实参)==>添加为执行上下文的属性
arguments==>赋值(实参列表), 添加为执行上下文的属性
var定义的局部变量==>undefined, 添加为执行上下文的属性
function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
this==>赋值(调用函数的对象)
开始执行函数体代码
代码演示:
代码一: console.log(a1) console.log(a2) console.log(a3) // console.log(a4)console.log(this) vara1=3vara2=function () { console.log('a2()') } functiona3() { console.log('a3()') } a4=4代码二: functionfn(x, y) { console.log(x, y) console.log(b1) console.log(b2) console.log(arguments) console.log(this) console.log(b3) varb1=5functionb2 () { } b3=6 } fn() **输出结果****代码一输出结果**
代码二输出结果
三.执行上下文栈
在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
在函数执行上下文创建后, 将其添加到栈中(压栈)
在当前函数执行完后,将栈顶的对象移除(出栈)
当所有的代码执行完后, 栈中只剩下window
执行上下文的个数公式n+1, n表示函数的调用次数,1表示windown
1.执行上下文测试题
递归练习
console.log('global begin: '+i) vari=1foo(1); functionfoo(i) { if (i==4) { return; } console.log('foo() begin:'+i); foo(i+1); console.log('foo() end:'+i); } console.log('global end: '+i)
输出结果:
图解:
四.作用域与作用域链
1.作用域
- 理解
- 就是一块"地盘", 一个代码段所在的区域
- 它是静态的(相对于上下文对象), 在编写代码时就确定了
- 分类
- 全局作用域
- 函数作用域
- 没有块作用域(ES6之前)
- 作用
- 隔离变量,不同作用域下同名变量不会有冲突
代码演示
vara=10, b=20functionfn(x) { vara=100, c=300; console.log('fn()', a, b, c, x). functionbar(x) { vara=1000, d=400console.log('bar()', a, b, c, d, x) } bar(100) bar(200) } fn(10)
输出结果:
2.作用域链
理解
多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
查找变量时就是沿着作用域链来查找的
查找一个变量的查找规则
在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常
代码演示:
vara=2; functionfn1() { varb=3; functionfn2() { varc=4; console.log(c); //4 console.log(b); //3console.log(a); //2console.log(d); //报错 } fn2(); } fn1();
输出结果:
😀作用域链测试题
- 题1
代码演示:
varx=10; functionfn() { console.log( ); } functionshow(f) { varx=20; f(); } show(fn);
输出结果
- 题2
代码演示
varfn=function () { console.log(fn) } fn() varobj= { fn2: function () { console.log(fn2) } } obj.fn2()
输出结果
五.闭包
1.闭包
如何产生闭包?
当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
闭包到底是什么?
使用chrome调试查看
理解一: 闭包是嵌套的内部函数(绝大部分人)
理解二: 包含被引用变量(函数)的对象(极少数人)
注意: 闭包存在于嵌套的内部函数中
产生闭包的条件?
函数嵌套
内部函数引用了外部函数的数据(变量/函数)
简单代码展示:
functionfn1 () { vara=3functionfn2 () { console.log(a) } } fn1()
2.常见的闭包
- 将函数作为另一个函数的返回值
- 将函数作为实参传递给另一个函数调用
代码演示:
第一种:functionfn1() { vara=2functionfn2() { a++console.log(a) } returnfn2} varf=fn1() f() // 3f() // 4第二种:functionshowMsgDelay(msg, time) { setTimeout(function () { console.log(msg) }, time) } showMsgDelay('hello', 1000) //hello
3.闭包的作用
- 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
- 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
代码演示:
functionfun1() { vara=3; functionfun2() { a++; //引用外部函数的变量--->产生闭包console.log(a); } returnfun2; } varf=fun1(); //由于f引用着内部的函数-->内部函数以及闭包都没有成为垃圾对象f(); //间接操作了函数内部的局部变量 4f(); //5
4.闭包的生命周期
- 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
- 死亡: 在嵌套的内部函数成为垃圾对象时
代码演示:
functionfun1() { 此处闭包已经产生vara=3; functionfun2() { a++; console.log(a); } returnfun2; } varf=fun1(); f(); //4f(); //5f=null此时闭包对象死亡
5.闭包的应用~js自定义模块
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包信n个方法的对象或函数
- 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
使用闭包封装js模块
引入js使用
输出的结果
6.闭包的缺点
- 缺点
- 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
- 容易造成内存泄露
- 解决
- 能不用闭包就不用
- 及时释放
😀练习题
- 说说它们的输出情况
代码演示:
//代码片段一varname="The Window"; varobject= { name: "My Object", getNameFunc: function () { returnfunction () { returnthis.name; }; } }; console.log(object.getNameFunc()()); //?//代码片段二varname2="The Window"; varobject2= { name2: "My Object", getNameFunc: function () { varthat=this; returnfunction () { returnthat.name2; }; } }; console.log(object2.getNameFunc()()); //?
输出结果:
😈终极面试题
代码及答案 如👇
functionfun(a,b){ console.log(b) return { fun:function (c) { returnfun(c,a) } } } // 测试1leta=fun(0) a.fun(1) a.fun(2) a.fun(3) // 答案 undefined 0 0 0;// 测试2letb=fun(0).fun(1).fun(2).fun(3) // 答案 undefined 1,2// 测试3letc=fun(0).fun(1) c.fun(2) c.fun(3) // 答案 undefined 0,1,1
三、总结
以上就是个人学习javaScript高级原型链相关的知识点,如有错漏之处,敬请指正”。