JavaScript复习,this指向、原型链、变量提升、作用域、闭包
文章目录
- 函数中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__继续往上找
看视频的话,应该容易理解该图。有基础的,看看就懂,虽然有点绕。
表达式: A instanceof B
意思是A是否是B的实例对象
如果B函数的显式原型对象在A对象的隐式原型链上, 返回true, 否则返回false。
具体就是A的__proto__(或者往上找)是否和B的prototype指向同一个东西。
- 看着上图,判断下面题目
/*案例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
- 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
- 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
- 函数执行上下文是在调用函数时, 函数体代码执行之前创建
- 区别2
- 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
- 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
- 联系
- 执行上下文(对象)是从属于所在的作用域
- 全局上下文环境==>全局作用域
- 函数上下文环境==>对应的函数使用域
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
都保留起来。
- 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包。
function (j)
属于父函数变量,(j + 1)
引用了父函数的变量。闭包到底是什么?
闭包是嵌套的内部函数(绝大部分人)
或者
包含被引用变量(函数)的对象(极少数人)
闭包存在于嵌套的内部函数中产生闭包的条件?
前提是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
这次重点不是在输出,而是理解下面问题:
在函数外部能直接访问函数内部的局部变量吗?
答:不能, 但我们可以通过闭包让外部操作它函数执行完后, 函数内部声明的局部变量是否还存在? (较难理解)
答:一般是不存在, 存在于闭包中的变量才可能存在
因为var f = fn1()
调用了父函数,毋庸置疑产生了闭包,闭包中的变量为var a
。
但是变量名fn2
,甚至是fn3
都不在闭包中。所以函数执行完都被清除。而由于var f = fn1()
,将原本fn3
引用的函数对象赋值给了var f
,所以该函数对象没有被当作垃圾对象,fn2
指向的函数对象则当作垃圾对象在内存中清除。
如果 fn1()
的返回值没有用变量接收,则函数执行完,fn1函数的局部变量都不存在,自然闭包也被清除了。
那么你会问到为什么又说只要满足产生闭包的条件,闭包就存在。
- 闭包的生命周期
产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
死亡: 在嵌套的内部函数成为垃圾对象时,即: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指向、原型链、变量提升、作用域、闭包相关推荐
- JavaScript之继承(原型链)
JavaScript之继承(原型链) 我们知道继承是oo语言中不可缺少的一部分,对于JavaScript也是如此.一般的继承有两种方式:其一,接口继承,只继承方法的签名:其二,实现继承,继承实际的方法 ...
- JavaScript高级day02-PM【原型链的属性问题、探索instanceof、原型面试题】
笔记.视频.源码:JavaScript(基础.高级)笔记汇总表[尚硅谷JavaScript全套教程完整版] 目录 P19 19.尚硅谷_JS高级_原型链_属性问题 11:53 P20 20.尚硅谷_J ...
- 【07】JavaScript:05-作用域、变量的作用域、作用域链、预解析、对象
文章目录 JavaScript基础第05天笔记 1 - 作用域 1.1 作用域概述 1.2 全局作用域 1.3 局部作用域 1.4 JS没有块级作用域 2 - 变量的作用域 3 - 作用域链 4 - ...
- JavaScript 笔记(2) -- 类型转换 正则表达 变量提升 表单验证
目录: typeof, null, undefined, valueOf() 类型转换 正则表达式 错误: try, catch, throw 调试工具 变量提升 strict 严格模式 使用误区 ...
- javascript 面向对象 new 关键字 原型链 构造函数
JavaScript面向对象 JavaScript 语言使用构造函数(constructor)作为对象的模板.所谓"构造函数",就是专门用来生成实例对象的函数.它就是对象的模板,描 ...
- JavaScript:变量提升作用域
作用域是JavaScript中听上去感觉很简单,其实比较麻烦的一个特性,什么是作用域?我看书籍有一个相对的官方解释:作用域(scope,或译有效范围)就是变量和函数的可访问范围,即作用域控制着变量和函 ...
- 精读《javascript高级程序设计》笔记二——变量、作用域、内存以及引用类型
变量.作用域和内存问题 执行环境共有两种类型--全局和局部 作用域链会加长,有两种情况:try-catch语句的catch块,with语句. javascript没有块级作用域,即在if,for循环中 ...
- JavaScript高级程序设计(4)变量、作用域和内存问题
本章内容:理解基本类型和引用类型的值:理解执行环境:理解垃圾收集. 基本类型和引用类型的值 ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值. 基本类型值:简单数据段:U ...
- 《JavaScript高级程序设计》笔记:变量、作用域和内存问题(四)
基本类型和引用类型的值 ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值.基本类型值指的是简单的数据段,而引用类型的值指那些可能有多个值构成的对象. 动态的属性 var p ...
- Javascript学习笔记(三)--变量、作用域和内存问题
一.变量 1.基本类型和引用类型的值 变量包含两种不同类型的值:基本类型值和引用类型值. 基本类型值包括:Undefined.Null.Boolean.Number和String(String在js属 ...
最新文章
- 技术14期:关于深度学习中耳熟能详的Embedding
- 随机选取字母c语言,菜鸟求助,写一个随机输出26个英文字母的程序
- [UVA 10827] Maximum sum on a torus
- 杭州电子科技大学-杭电信标一队
- fifo 上使用 select -- 转
- 解决System.Data.SqlClient.SqlException (0x80131904): Timeout 时间已到的问题
- Xamarin.iOS编译时无法连接苹果系统
- Java 11都有哪些新特性?
- spring mvc 总体启动流程
- IOS开发基础之时钟的实现
- LeetCode 1490. 克隆 N 叉树(DFS/BFS)
- LeetCode 第 18 场双周赛(188/587,前32%)
- 单片机拼字程序怎么做_餐饮怎么用微信小程序?餐饮行业怎么做小程序
- ubuntu环境搭建五:修改中文字符集
- butterfly配置 hexo_Hexo博客之butterfly主题优雅魔改系列(持续更新)
- 解决Maven:com.oracle:ojdbc7-9.9.9.jar在pom文件无法下载问题
- win7系统任务栏不见了怎么办
- mysql基本50题_mysql-50题
- Linux下串口编制【转】
- 个人家用nas_希捷个人云评测:家用NAS中的佼佼者
热门文章
- #学习笔记 使用c语言来制作一个计算器
- 西电c1级计算机应用测试题型,西电计算机应用基础测试题
- P2433 【深基1-2】小学数学 N 合一
- centos7安装erlang
- WordPress主题制作全过程(一):基础准备
- 罗振宇2018“时间的朋友”跨年演讲未删减全文
- Win10 VS2019+QT/OpenCV/灰点相机/函数信号发生器 配置及其使用
- [Multisim][模电实验]简易函数信号发生器的设计与实现_北京邮电大学2019级信通院电子电路实验下
- 手机上计算机怎么设置,电脑如何设置wifi让手机上网
- 家谱制作软件如何下载及安装