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

输出结果:

![在这里插入图片描述](https://img-blog.csdnimg.cn/

😈终极面试题

代码及答案 如👇

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

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SYFStrive

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值