javaScript函数高级
✨文章有误请指正,如果觉得对你有用,请点赞收藏关注一波,谢谢支持😘
前言
js中的原型毫无疑问一个难点,学习如果不掌握原理就容易晕!可能这时候懂,等几个小时过后再来写就很容易蒙,任何一个js知识点,比如学习事件流,闭包,继承等,对于这些知识点我们都应该先熟练原理,然后自己整理一套属于自己的理解说辞,才不会忘。
以下是我学习JavaScript高级原型链以及闭包的知识笔记
一、函数原型与原型链
一.原型
1.原型prototype属性
- 函数的prototype属性(图)
- 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
代码演示:
例一:
console.log(Date.prototype, typeof Date.prototype)
输出的结果:
Object {constructor: Function, toString: Function, toDateString:
Function, toTimeString: Function, toISOString: Function, ...} object
例二:
function fn() {
}
console.log(fn.prototype, typeof fn.prototype)
输出的结果:
Object {constructor: Function} object
- 原型对象中有一个属性constructor, 它指向函数对象
代码演示:
console.log(Date.prototype.constructor===Date)
console.log(fn.prototype.constructor===fn)
* 给原型对象添加属性(一般都是方法)
> 代码演示
```javaScript
function F() {
}
F.prototype.age = 12 //添加属性
F.prototype.setAge = function (age) { // 添加方法
this.age = age
}
// 创建函数的实例对象
var f = new F()
console.log(f.age) //12
f.setAge(23)
console.log(f.age) //23
2.显示原型与隐式原型
- 每个函数function都有一个prototype,即显式原型
- 每个实例对象都有一个__proto__,可称为隐式原型
- 对象的隐式原型的值为其对应构造函数的显式原型的值
- 内存结构(图)
总结:- 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
- 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
- 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
代码举例:
例子原型链图示:
二.原型链
- 原型链
- 访问一个对象的属性时,
- 先在自身属性中查找,找到返回
- 如果没有, 再沿着__proto__这条链向上查找, 找到返回
- 如果最终没找到, 返回undefined
- 别名: 隐式原型链
- 作用: 查找对象的属性(方法)
- 访问一个对象的属性时,
代码演示:
function Fn() {
this.test1 = function () {
console.log('test1()') //输出1
}
}
Fn.prototype.test2 = function () {
console.log('test2()') //输出2
}
var fn = new Fn()
fn.test1()
fn.test2()
console.log(fn.toString()) //输出3
fn.test3() //输出
例子原型链图示:
1.原型链的属性问题
- 读取对象的属性值时: 会自动到原型链中查找
- 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
- 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
代码演示:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.setName = function (name) {
this.name = name;
}
Person.prototype.sex = '男';
let p1 = new Person('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 女
let p2 = new Person('Bob', 23)
console.log(p2.name, p2.age, p2.sex) ///Bob 23 男
2.探究instanceof
- 表达式: A instanceof B
- 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
- Function是通过new自己产生的实例
代码演示:
//案例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 Object) //true
console.log(Function instanceof Function) //true
function Foo() {}
console.log(Object instanceof Foo); //false
instanceof案例原型图
😀原型测试题
- 测试题
案例:
测试一:
let A = function() {
}
A.prototype.n = 1
let b = new A()
A.prototype = {
n: 2,
m: 3
}
let c = new A()
console.log(b.n, b.m, c.n, c.m) //1 undefined 2 3
————————————————————————————————————————————
测试二:
let F = function(){};
Object.prototype.a = function(){
console.log('a()')
};
Function.prototype.b = function(){
console.log('b()')
};
let f = new F();
f.a() //a()
f.b() //Uncaught TypeError: f.b is not a function
F.a() //a()
F.b() //b()
图示输出结果:
测试一:
测试二:
二、执行上下文与执行上下文栈
一.变量的提升与函数提升
- 变量声明提升
- 通过var定义(声明)的变量, 在定义语句之前就可以访问到
- 值: undefined
- 函数声明提升
- 通过function声明的函数, 在之前就可以直接调用
- 值: 函数定义(对象)
- 问题: 变量提升和函数提升是如何产生的?
代码演示:
代码一:
var a = 4 这里变量提升
function fn () {
console.log(a) 可以访问a, 但值是undefined
var a = 5 这里变量提升
}
fn()
代码二:
console.log(a1) 可以访问a1, 但值是undefined
a2()
var a1 = 3 这里变量提升
function a2() { 这里函数提升
console.log('a2()')
}
图示
代码一图解:
代码二图解:
😀变量提升函数提升测试题
- 变量提升函数提升题目
代码演示:
var c = 1 变量提升
function c(c) { 函数提升
console.log(c) 报错
var c = 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)
var a1 = 3
var a2 = function () {
console.log('a2()')
}
function a3() {
console.log('a3()')
}
a4 = 4
代码二:
function fn(x, y) {
console.log(x, y)
console.log(b1)
console.log(b2)
console.log(arguments)
console.log(this)
console.log(b3)
var b1 = 5
function b2 () {
}
b3 = 6
}
fn()
**输出结果**
**代码一输出结果**
代码二输出结果
三.执行上下文栈
- 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
- 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
- 在函数执行上下文创建后, 将其添加到栈中(压栈)
- 在当前函数执行完后,将栈顶的对象移除(出栈)
- 当所有的代码执行完后, 栈中只剩下window
- 执行上下文的个数公式n+1, n表示函数的调用次数,1表示windown
1.执行上下文测试题
递归练习
> 代码演示
console.log('global begin: '+ i)
var i = 1
foo(1);
function foo(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之前)
- 作用
- 隔离变量,不同作用域下同名变量不会有冲突
代码演示
- 隔离变量,不同作用域下同名变量不会有冲突
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)
输出结果:
2.作用域链
- 理解
- 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
- 查找变量时就是沿着作用域链来查找的
- 查找一个变量的查找规则
- 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
- 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
- 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常
代码演示:
var a = 2;
function fn1() {
var b = 3;
function fn2() {
var c = 4;
console.log(c); //4
console.log(b); //3
console.log(a); //2
console.log(d); //报错
}
fn2();
}
fn1();
输出结果:
😀作用域链测试题
- 题1
代码演示:
var x = 10;
function fn() {
console.log(
);
}
function show(f) {
var x = 20;
f();
}
show(fn);
输出结果
- 题2
代码演示
var fn = function () {
console.log(fn)
}
fn()
var obj = {
fn2: function () {
console.log(fn2)
}
}
obj.fn2()
输出结果
五.闭包
1.闭包
- 如何产生闭包?
- 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
- 闭包到底是什么?
- 使用chrome调试查看
- 理解一: 闭包是嵌套的内部函数(绝大部分人)
- 理解二: 包含被引用变量(函数)的对象(极少数人)
- 注意: 闭包存在于嵌套的内部函数中
- 产生闭包的条件?
- 函数嵌套
- 内部函数引用了外部函数的数据(变量/函数)
简单代码展示:
function fn1 () {
var a = 3
function fn2 () {
console.log(a)
}
}
fn1()
2.常见的闭包
- 将函数作为另一个函数的返回值
- 将函数作为实参传递给另一个函数调用
代码演示:
第一种:
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
第二种:
function showMsgDelay(msg, time) {
setTimeout(function () {
console.log(msg)
}, time)
}
showMsgDelay('hello', 1000) //hello
3.闭包的作用
- 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
- 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
代码演示:
function fun1() {
var a = 3;
function fun2() {
a++; //引用外部函数的变量--->产生闭包
console.log(a);
}
return fun2;
}
var f = fun1(); //由于f引用着内部的函数-->内部函数以及闭包都没有成为垃圾对象
f(); //间接操作了函数内部的局部变量 4
f(); //5
4.闭包的生命周期
- 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
- 死亡: 在嵌套的内部函数成为垃圾对象时
代码演示:
function fun1() {
此处闭包已经产生
var a = 3;
function fun2() {
a++;
console.log(a);
}
return fun2;
}
var f = fun1();
f(); //4
f(); //5
f = null 此时闭包对象死亡
5.闭包的应用~js自定义模块
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包信n个方法的对象或函数
- 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
使用闭包封装js模块
引入js使用
。
输出的结果
6.闭包的缺点
- 缺点
- 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
- 容易造成内存泄露
- 解决
- 能不用闭包就不用
- 及时释放
😀练习题
- 说说它们的输出情况
代码演示:
//代码片段一
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name;
};
}
};
console.log(object.getNameFunc()()); //?
//代码片段二
var name2 = "The Window";
var object2 = {
name2: "My Object",
getNameFunc: function () {
var that = this;
return function () {
return that.name2;
};
}
};
console.log(object2.getNameFunc()()); //?
输出结果:
😈终极面试题
代码及答案 如👇
function fun(a,b){
console.log(b)
return {
fun:function (c) {
return fun(c,a)
}
}
}
// 测试1
let a = fun(0)
a.fun(1)
a.fun(2)
a.fun(3)
// 答案 undefined 0 0 0;
// 测试2
let b = fun(0).fun(1).fun(2).fun(3)
// 答案 undefined 1,2
// 测试3
let c = fun(0).fun(1)
c.fun(2)
c.fun(3)
// 答案 undefined 0,1,1
三、总结
以上就是个人学习javaScript高级原型链相关的知识点,如有错漏之处,敬请指正”。