文章目录

  • 函数中this指向问题
  • 原型
  • 变量/函数 提升
    • 全局执行上下文:
    • 测试题
    • var可重复声明
  • 作用域
  • 闭包
    • 更深入理解闭包的原理
    • 闭包的作用
    • 终极题目
  • ES5的继承

函数中this指向问题

任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window

  Person("red"); //this是谁? windowvar p = new Person("yello"); //this是谁? pp.getColor(); //this是谁? pvar obj = {};p.setColor.call(obj, "black"); //this是谁? objvar test = p.setColor;test(); //this是谁? window
var name = 'window'
var person = {name :'Alan',sayName:function () {return function () {console.log(this.name)}}
}
person.sayName()()  // window

这里sayName方法的确是person调用的,但返回的匿名函数是window调用,这个匿名函数中this指向window

解决方法还是有的,我们可以把外部作用域的this传递给匿名函数。

var name2 = "The Window";
var object2 = {name2 : "My Object",getNameFunc : function(){var that = this;return function(){return that.name2;};}
};
alert(object2.getNameFunc()()); //  my object

函数嵌套,使用了闭包的原理

原型

函数也属于对象

每个函数都有一个prototype属性, 它默认指向一个Object空的实例对象(即称为: 原型对象)

原型对象中有一个属性constructor, 它指回函数对象

每个函数function都有一个prototype,即显式原型(属性)
在定义函数时自动添加的, 默认值是一个空Object实例对象

每个实例对象都有一个__proto__,可称为隐式原型(属性)
创建实例对象时自动添加的, 默认值为构造函数的prototype属性值

实例对象的隐式原型的值===其对应构造函数的显式原型的值,即指向同一个东西

程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)

原型链的查找方式时沿着__proto__这条链向上查找,所以也称隐式原型链

理解该图:

  • function Foo()它是一个构造函数,但同时也可以看成是Function构造函数的一个实例对象:Foo = new Function。所以Foo实例对象也有隐式原型__proto__
  • 所有函数都是Function的实例(包含Function,Function = new Function;Object = new Function)
  • 函数的显示原型指向的对象默认是空的Object实例对象,即Foo.prototype和Funtion.prototype为Object实例对象。
    (但Object不满足,因为Object.prototype的__proto__值为null,说明它不是实例对象)
  • Object的原型对象是原型链尽头,因为它不是实例对象,没有__proto__继续往上找


看视频的话,应该容易理解该图。有基础的,看看就懂,虽然有点绕。

  1. 表达式: A instanceof B 意思是A是否是B的实例对象

如果B函数的显式原型对象在A对象的隐式原型链上, 返回true, 否则返回false。
具体就是A的__proto__(或者往上找)是否和B的prototype指向同一个东西。

  1. 看着上图,判断下面题目
  /*案例2*/console.log(Object instanceof Function) // trueconsole.log(Object instanceof Object) // trueconsole.log(Function instanceof Function) // trueconsole.log(Function instanceof Object) // true

1.读取对象的属性值时: 会自动到原型链中查找
2.设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值

  function Fn() {}Fn.prototype.a = 'xxx'var fn1 = new Fn()console.log(fn1.a, fn1)var fn2 = new Fn()fn2.a = 'yyy'console.log(fn1.a, fn2.a, fn2)// 结果// xxx  yyy

  function A () {}A.prototype.n = 1var b = new A()A.prototype = {n: 2,m: 3}var c = new A()console.log(b.n, b.m, c.n, c.m)// 结果// 1 undefined 2 3// 原因是 b的proto引用是之前的原型,c的proto引用是新的原型
  function F (){}Object.prototype.a = function(){console.log('a()')}Function.prototype.b = function(){console.log('b()')}var f = new F()f.a()// f.b() b为undefinedF.a()F.b()

变量/函数 提升

console.log(b) //undefined  变量提升fn2() //可调用  函数提升// fn3() //不能  因为fn3属于变量提升var b = 3function fn2() {console.log('fn2()')}var fn3 = function () {console.log('fn3()')}

全局执行上下文:

  • 在执行全局代码前将window确定为全局执行上下文

    函数执行上下文:
  • 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)

测试题

 /*测试题1:  先执行变量提升, 再执行函数提升*/function a() { }var aconsole.log(typeof a) // 'function'/*测试题2: var b做变量提升,不进入if语句*/if (!(b in window)) {var b = 1}console.log(b) // undefined/*测试题3:顺序为 var c; fun c; c=1*/var c = 1function c(c) {console.log(c)var c = 3}c(2) // 报错

var可重复声明

function c(c) {console.log(c) // 2var c = 5console.log(c) // 5}c(2)
function fn(a){console.log(typeof a) // "function"var afunction a() {console.log(3);}console.log(typeof a) //"function"
}
fn(5)

输出为 “function”,说明先形参赋值 后函数提升赋值

var a = 100;
function fn() {alert(a); //undefined
var a = 200;
alert(a); //200
}
fn();
alert(a); //100
var a;
alert(a); //100
var a = 300;
alert(a); //300如果重复使用的一个声明有一个初始值,那么它担当的不过是一个赋值语句的角色.
如果重复使用的一个声明没有一个初始值,那么它不会对原来存在的变量有任何的影响.

1.使用var声明变量,在方法内部是局部变量,在方法外部是全局变量
2.没有使用var声明的变量,在方法内部或外部都是全局变量,但如果是在方
法内部声明,在方法外部使用之前 需要先调用方法,告知系统声明了全局变量 后方可在方法外部使用。

// 代码报错。可见没有var关键字,变量的声明不会提前
console.log(d) //Uncaught ReferenceError: d is not defined
d = 1
 // var val,使函数里面的val都为局部变量function TestClass() {val = 1;alert(val);//1  alert(window.val);//undefined  var val = 10;alert(val);//10  alert(window.val);//undefined  }var test = new TestClass();
var n1 = 1;n2 = 2;function f1() {var n3 = 3;n4 = 4;}//   console.log("n4="+n4) //Error: n4 is not definedf1();console.log("n1=" + n1);console.log("n2=" + n2);//     console.log("n3="+n3); //Error: n3 is not definedconsole.log("n4=" + n4);

作用域

  1. 区别1
  • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
  • 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
  • 函数执行上下文是在调用函数时, 函数体代码执行之前创建
  1. 区别2
  • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
  • 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
  1. 联系
  • 执行上下文(对象)是从属于所在的作用域
  • 全局上下文环境==>全局作用域
  • 函数上下文环境==>对应的函数使用域
  var x = 10;function fn() {console.log(x);}function show(f) {var x = 20;f();}show(fn);

输出结果:10

    var fn = function () {console.log(fn)}fn()var obj = {fn2: function () {console.log(this.fn2)console.log(fn2)}}obj.fn2()

fn2首先在函数作用域找不到,然后全局作用域也找不到。

闭包

需求: 点击某个按钮, 提示"点击的是第n个按钮"

//利用闭包for (var i = 0, length = btns.length; i < length; i++) {(function (j) {var btn = btns[j]btn.onclick = function () {alert('第' + (j + 1) + '个')}})(i)}

看完下面知道怎么解释了,之前我只是理解为像let关键字一样,每次循环的j都保留起来。

  1. 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包。

function (j)属于父函数变量,(j + 1)引用了父函数的变量。闭包到底是什么?

  1. 闭包是嵌套的内部函数(绝大部分人)
    或者
    包含被引用变量(函数)的对象(极少数人)
    闭包存在于嵌套的内部函数中

  2. 产生闭包的条件?

前提是function (j) 要先执行,然后btn.onclick = function ()执行函数定义就会产生闭包(不用调用内部函数)。比如上述,点击事件并未触发,但已经产生闭包了。

常见闭包

  // 2. 将函数作为实参传递给另一个函数调用function showDelay(msg, time) {setTimeout(function () {alert(msg)}, time)}showDelay('atguigu', 2000)

更深入理解闭包的原理

// 1. 将函数作为另一个函数的返回值function fn1() {var a = 2function fn2() {a++console.log(a)}return fn2}var f = fn1()f() // 3f() // 4var f2 = fn1()f2()f2()// 结果// 3 4 3 4

说明,每次调用父函数,都生成新的闭包。也可以认为不同的区域,所以f的闭包与f2的闭包无关

    // 1. 将函数作为另一个函数的返回值function fn1() {var a = 2function fn2() {a++console.log(a)}function fn3() {a--console.log(a)}return fn3}var f = fn1()f() // 1f() // 0

这次重点不是在输出,而是理解下面问题:

  1. 在函数外部能直接访问函数内部的局部变量吗?
    答:不能, 但我们可以通过闭包让外部操作它

  2. 函数执行完后, 函数内部声明的局部变量是否还存在? (较难理解)
    答:一般是不存在, 存在于闭包中的变量才可能存在

因为var f = fn1()调用了父函数,毋庸置疑产生了闭包,闭包中的变量为var a

但是变量名fn2,甚至是fn3都不在闭包中。所以函数执行完都被清除。而由于var f = fn1(),将原本fn3引用的函数对象赋值给了var f,所以该函数对象没有被当作垃圾对象,fn2指向的函数对象则当作垃圾对象在内存中清除。

如果 fn1()的返回值没有用变量接收,则函数执行完,fn1函数的局部变量都不存在,自然闭包也被清除了。

那么你会问到为什么又说只要满足产生闭包的条件,闭包就存在。

  1. 闭包的生命周期
    产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
    死亡: 在嵌套的内部函数成为垃圾对象时,即:var f = fn1()改为 fn1()
  function fn1() {//此时闭包就已经产生了(函数提升, 内部函数对象已经创建了)var a = 2function fn2 () {a++console.log(a)}return fn2}var f = fn1()f() // 3f() // 4f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)

我感觉之前黑马的pink老师确实没这么细,导致看完视频很多都忘记了。一定要明白原理,才会印象深刻。

闭包的作用

八股文

  • 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
  • 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
  • 具有特定功能的js文件,将所有的数据和功能都封装在一个函数内部(私有的),只向外暴露一个包信n个方法的对象或函数

缺点:

  • 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长

终极题目

贴士:
不要认为它是一个类,每次调用父函数都会产生新的闭包。
如果你不用变量去接收内部函数,那么内部函数就会清除掉,新产生的闭包也会清除。

  function fun(n,o) {console.log(o)return {fun:function(m){return fun(m,n)}}}var a = fun(0)a.fun(1)a.fun(2)a.fun(3)//undefined,0,0,0var b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2var c = fun(0).fun(1)c.fun(2)c.fun(3)//undefined,0,1,1

ES5的继承

<!--
方式3: 原型链+借用构造函数的组合继承
1. 利用原型链实现对父类型对象的方法继承
2. 利用call()借用父类型构建函数初始化相同属性
-->
<script type="text/javascript">function Person(name, age) {this.name = namethis.age = age}Person.prototype.setName = function (name) {this.name = name}function Student(name, age, price) {Person.call(this, name, age)  // 为了得到属性this.price = price}Student.prototype = new Person() // 为了能看到父类型的方法Student.prototype.constructor = Student //修正constructor属性Student.prototype.setPrice = function (price) {this.price = price}var s = new Student('Tom', 24, 15000)s.setName('Bob')s.setPrice(16000)console.log(s.name, s.age, s.price)

JavaScript复习,this指向、原型链、变量提升、作用域、闭包相关推荐

  1. JavaScript之继承(原型链)

    JavaScript之继承(原型链) 我们知道继承是oo语言中不可缺少的一部分,对于JavaScript也是如此.一般的继承有两种方式:其一,接口继承,只继承方法的签名:其二,实现继承,继承实际的方法 ...

  2. JavaScript高级day02-PM【原型链的属性问题、探索instanceof、原型面试题】

    笔记.视频.源码:JavaScript(基础.高级)笔记汇总表[尚硅谷JavaScript全套教程完整版] 目录 P19 19.尚硅谷_JS高级_原型链_属性问题 11:53 P20 20.尚硅谷_J ...

  3. 【07】JavaScript:05-作用域、变量的作用域、作用域链、预解析、对象

    文章目录 JavaScript基础第05天笔记 1 - 作用域 1.1 作用域概述 1.2 全局作用域 1.3 局部作用域 1.4 JS没有块级作用域 2 - 变量的作用域 3 - 作用域链 4 - ...

  4. JavaScript 笔记(2) -- 类型转换 正则表达 变量提升 表单验证

    目录:  typeof, null, undefined, valueOf() 类型转换 正则表达式 错误: try, catch, throw 调试工具 变量提升 strict 严格模式 使用误区 ...

  5. javascript 面向对象 new 关键字 原型链 构造函数

    JavaScript面向对象 JavaScript 语言使用构造函数(constructor)作为对象的模板.所谓"构造函数",就是专门用来生成实例对象的函数.它就是对象的模板,描 ...

  6. JavaScript:变量提升作用域

    作用域是JavaScript中听上去感觉很简单,其实比较麻烦的一个特性,什么是作用域?我看书籍有一个相对的官方解释:作用域(scope,或译有效范围)就是变量和函数的可访问范围,即作用域控制着变量和函 ...

  7. 精读《javascript高级程序设计》笔记二——变量、作用域、内存以及引用类型

    变量.作用域和内存问题 执行环境共有两种类型--全局和局部 作用域链会加长,有两种情况:try-catch语句的catch块,with语句. javascript没有块级作用域,即在if,for循环中 ...

  8. JavaScript高级程序设计(4)变量、作用域和内存问题

    本章内容:理解基本类型和引用类型的值:理解执行环境:理解垃圾收集.  基本类型和引用类型的值 ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值. 基本类型值:简单数据段:U ...

  9. 《JavaScript高级程序设计》笔记:变量、作用域和内存问题(四)

    基本类型和引用类型的值 ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值.基本类型值指的是简单的数据段,而引用类型的值指那些可能有多个值构成的对象. 动态的属性 var p ...

  10. Javascript学习笔记(三)--变量、作用域和内存问题

    一.变量 1.基本类型和引用类型的值 变量包含两种不同类型的值:基本类型值和引用类型值. 基本类型值包括:Undefined.Null.Boolean.Number和String(String在js属 ...

最新文章

  1. 技术14期:关于深度学习中耳熟能详的Embedding
  2. 随机选取字母c语言,菜鸟求助,写一个随机输出26个英文字母的程序
  3. [UVA 10827] Maximum sum on a torus
  4. 杭州电子科技大学-杭电信标一队
  5. fifo 上使用 select -- 转
  6. 解决System.Data.SqlClient.SqlException (0x80131904): Timeout 时间已到的问题
  7. Xamarin.iOS编译时无法连接苹果系统
  8. Java 11都有哪些新特性?
  9. spring mvc 总体启动流程
  10. IOS开发基础之时钟的实现
  11. LeetCode 1490. 克隆 N 叉树(DFS/BFS)
  12. LeetCode 第 18 场双周赛(188/587,前32%)
  13. 单片机拼字程序怎么做_餐饮怎么用微信小程序?餐饮行业怎么做小程序
  14. ubuntu环境搭建五:修改中文字符集
  15. butterfly配置 hexo_Hexo博客之butterfly主题优雅魔改系列(持续更新)
  16. 解决Maven:com.oracle:ojdbc7-9.9.9.jar在pom文件无法下载问题
  17. win7系统任务栏不见了怎么办
  18. mysql基本50题_mysql-50题
  19. Linux下串口编制【转】
  20. 个人家用nas_希捷个人云评测:家用NAS中的佼佼者

热门文章

  1. #学习笔记 使用c语言来制作一个计算器
  2. 西电c1级计算机应用测试题型,西电计算机应用基础测试题
  3. P2433 【深基1-2】小学数学 N 合一
  4. centos7安装erlang
  5. WordPress主题制作全过程(一):基础准备
  6. 罗振宇2018“时间的朋友”跨年演讲未删减全文
  7. Win10 VS2019+QT/OpenCV/灰点相机/函数信号发生器 配置及其使用
  8. [Multisim][模电实验]简易函数信号发生器的设计与实现_北京邮电大学2019级信通院电子电路实验下
  9. 手机上计算机怎么设置,电脑如何设置wifi让手机上网
  10. 家谱制作软件如何下载及安装