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


相关文章
|
2月前
|
JavaScript 前端开发 开发者
理解JavaScript中的原型链:基础与实践
【10月更文挑战第8天】理解JavaScript中的原型链:基础与实践
|
1月前
|
JavaScript 前端开发
JavaScript 原型链的实现原理是什么?
JavaScript 原型链的实现原理是通过构造函数的`prototype`属性、对象的`__proto__`属性以及属性查找机制等相互配合,构建了一个从对象到`Object.prototype`的链式结构,实现了对象之间的继承、属性共享和动态扩展等功能,为 JavaScript 的面向对象编程提供了强大的支持。
|
1月前
|
JavaScript 前端开发
原型链在 JavaScript 中的作用是什么?
原型链是 JavaScript 中实现面向对象编程的重要机制之一,它为代码的组织、复用、扩展和多态性提供了强大的支持,使得 JavaScript 能够以简洁而灵活的方式构建复杂的应用程序。深入理解和熟练运用原型链,对于提升 JavaScript 编程能力和开发高质量的应用具有重要意义。
|
1月前
|
JavaScript 前端开发
如何使用原型链继承实现 JavaScript 继承?
【10月更文挑战第22天】使用原型链继承可以实现JavaScript中的继承关系,但需要注意其共享性、查找效率以及参数传递等问题,根据具体的应用场景合理地选择和使用继承方式,以满足代码的复用性和可维护性要求。
|
2月前
|
JavaScript 前端开发 开发者
探索JavaScript原型链:深入理解与实战应用
【10月更文挑战第21天】探索JavaScript原型链:深入理解与实战应用
32 1
|
2月前
|
JavaScript 前端开发 开发者
深入理解JavaScript原型链:从基础到进阶
【10月更文挑战第13天】深入理解JavaScript原型链:从基础到进阶
28 0
|
2月前
|
JavaScript 前端开发 开发者
原型链深入解析:JavaScript中的核心机制
【10月更文挑战第13天】原型链深入解析:JavaScript中的核心机制
35 0
|
2月前
|
JavaScript 前端开发 安全
深入理解JavaScript原型链:从基础到进阶
【10月更文挑战第13天】深入理解JavaScript原型链:从基础到进阶
30 0
|
4月前
|
开发者 图形学 iOS开发
掌握Unity的跨平台部署与发布秘籍,让你的游戏作品在多个平台上大放异彩——从基础设置到高级优化,深入解析一站式游戏开发解决方案的每一个细节,带你领略高效发布流程的魅力所在
【8月更文挑战第31天】跨平台游戏开发是当今游戏产业的热点,尤其在移动设备普及的背景下更为重要。作为领先的游戏开发引擎,Unity以其卓越的跨平台支持能力脱颖而出,能够将游戏轻松部署至iOS、Android、PC、Mac、Web及游戏主机等多个平台。本文通过杂文形式探讨Unity在各平台的部署与发布策略,并提供具体实例,涵盖项目设置、性能优化、打包流程及发布前准备等关键环节,助力开发者充分利用Unity的强大功能,实现多平台游戏开发。
113 0
|
4月前
|
JavaScript 前端开发 开发者
揭开JavaScript的神秘面纱:原型链背后隐藏的继承秘密
【8月更文挑战第23天】原型链是JavaScript面向对象编程的核心特性,它使对象能继承另一个对象的属性和方法。每个对象内部都有一个[[Prototype]]属性指向其原型对象,形成链式结构。访问对象属性时,若当前对象不存在该属性,则沿原型链向上查找。
35 0