JavaScript高级_原型链②

简介: JavaScript高级_原型链②

前言


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之前)

代码举例:

image.png

image.png

例子原型链图示:

image.png

二.原型链


原型链

  • 访问一个对象的属性时,
  • 先在自身属性中查找,找到返回
  • 如果没有, 再沿着__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()   //输出

image.png

例子原型链图示:

1.png

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案例原型图

image.png

😀原型测试题

测试题

案例:

测试一:
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()

图示输出结果:

测试一:

image.png

测试二:

image.png

二、执行上下文与执行上下文栈


一.变量的提升与函数提升


变量声明提升

  • 通过var定义(声明)的变量, 在定义语句之前就可以访问到
  • 值: undefined
  • 函数声明提升
  • 通过function声明的函数, 在之前就可以直接调用
  • 值: 函数定义(对象)
  • 问题: 变量提升和函数提升是如何产生的?

代码演示:

代码一:
vara=4这里变量提升functionfn () {
console.log(a)   可以访问a, 但值是undefinedvara=5这里变量提升}
fn()
代码二:
console.log(a1) 可以访问a1, 但值是undefineda2()
vara1=3这里变量提升functiona2() {   这里函数提升console.log('a2()')
  }

图示

代码一图解:

image.png

代码二图解:

image.png

😀变量提升函数提升测试题

  • 变量提升函数提升题目

代码演示:

varc=1变量提升functionc(c) {  函数提升console.log(c)  报错varc=3 }
c(2)

输出结果

image.png

二.执行上下文


代码分类(位置)

全局代码

函数代码

全局执行上下文

在执行全局代码前将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()
**输出结果****代码一输出结果**

image.png

代码二输出结果

image.png

三.执行上下文栈


在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象

在全局执行上下文(window)确定后, 将其添加到栈中(压栈)

在函数执行上下文创建后, 将其添加到栈中(压栈)

在当前函数执行完后,将栈顶的对象移除(出栈)

当所有的代码执行完后, 栈中只剩下window

执行上下文的个数公式n+1, n表示函数的调用次数,1表示windown

2.png

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)

输出结果:

image.png

图解:

image.png

四.作用域与作用域链


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)

输出结果:

image.png

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();

输出结果:

image.png

😀作用域链测试题
  • 题1

代码演示:

varx=10;
functionfn() {
console.log(
    );
  }
functionshow(f) {
varx=20;
f();
  }     
show(fn);

输出结果

image.png

  • 题2

代码演示

varfn=function () {
console.log(fn)
 }
fn()
varobj= {
fn2: function () {
console.log(fn2)
  }
}
obj.fn2()

输出结果

image.png

五.闭包


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模块

image.png

引入js使用

image.png

输出的结果

image.png

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()()); //?

输出结果:

image.png

😈终极面试题

代码及答案 如👇

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高级原型链相关的知识点,如有错漏之处,敬请指正”。


相关文章
|
6天前
|
JavaScript 前端开发
谈谈对 JavaScript 中的原型链的理解。
JavaScript中的原型链是实现继承和共享属性的关键机制,它通过对象的`prototype`属性连接原型对象。当访问对象属性时,若对象本身没有该属性,则会查找原型链。此机制减少内存占用,实现代码复用。例如,实例对象可继承原型对象的方法。原型链也用于继承,子类通过原型链获取父类属性和方法。然而,原型属性共享可能导致数据冲突,且查找过程可能影响性能。理解原型链对JavaScript面向对象编程至关重要。如有更多问题,欢迎继续探讨😊
16 3
|
6天前
|
JavaScript 前端开发 安全
JavaScript原型链的使用
【4月更文挑战第22天】JavaScript中的原型链是理解继承的关键,它允许对象复用属性和方法,减少代码冗余。示例展示如何通过原型链实现继承、扩展内置对象、构造函数与原型链的关系以及查找机制。应注意避免修改`Object.prototype`,使用安全方式设置原型链,并谨慎处理构造函数和副作用。
|
3天前
|
前端开发 JavaScript
JavaScript新科技:PostCSS的安装和使用,2024年最新2024网易Web前端高级面试题总结
JavaScript新科技:PostCSS的安装和使用,2024年最新2024网易Web前端高级面试题总结
|
3天前
|
JavaScript 前端开发
web前端JS高阶面试题(1),高级开发工程师面试
web前端JS高阶面试题(1),高级开发工程师面试
|
3天前
|
前端开发 JavaScript
前端 js 经典:原型对象和原型链
前端 js 经典:原型对象和原型链
14 1
|
4天前
|
JavaScript 前端开发
JavaScript 原型链继承:掌握面向对象的基础
JavaScript 原型链继承:掌握面向对象的基础
|
6天前
|
JavaScript 前端开发
JavaScript原型链:工作原理与深入探究
【4月更文挑战第22天】JavaScript原型链是对象属性查找的关键,它通过对象间的链接形成链式结构。当访问属性时,JS从对象自身开始查找,若未找到则沿原型链向上搜索,直至`null`。原型链用于继承、扩展内置对象和实现多态,但要注意避免修改内置对象原型、控制链长度及使用`Object.create()`创建对象。理解并合理运用原型链能深化JS面向对象编程的理解。
|
6天前
|
JavaScript
什么是js的原型链
什么是js的原型链
|
6天前
|
JavaScript 前端开发
JavaScript高级主题:什么是 ES6 的解构赋值?
【4月更文挑战第13天】ES6的解构赋值语法简化了从数组和对象中提取值的过程,提高代码可读性。例如,可以从数组`[1, 2, 3]`中分别赋值给`a`, `b`, `c`,或者从对象`{x: 1, y: 2, z: 3}`中提取属性值给同名变量。
18 6
|
6天前
|
存储 JavaScript 前端开发
JavaScript高级主题:JavaScript 中的 Map 和 Set 是什么?它们有什么区别?
JavaScript的ES6引入了Map和Set数据结构。Map用于存储键值对,适合通过键进行查找,而Set则存储唯一值,无键且不支持键查找。两者在性能上表现出色,尤其在频繁的写入删除操作中。选择使用哪个取决于具体应用场景:键值对需求选Map,独特值集合则选Set。
20 2