函数中this指向问题
任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window
Person("red"); //this是谁? window
var p = new Person("yello"); //this是谁? p
p.getColor(); //this是谁? p
var obj = {};
p.setColor.call(obj, "black"); //this是谁? obj
var 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) // true
console.log(Object instanceof Object) // true
console.log(Function instanceof Function) // true
console.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 = 1
var 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为undefined
F.a()
F.b()
变量/函数 提升
console.log(b) //undefined 变量提升
fn2() //可调用 函数提升
// fn3() //不能 因为fn3属于变量提升
var b = 3
function fn2() {
console.log('fn2()')
}
var fn3 = function () {
console.log('fn3()')
}
全局执行上下文:
- 在执行全局代码前将window确定为全局执行上下文
函数执行上下文: - 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
测试题
/*
测试题1: 先执行变量提升, 再执行函数提升
*/
function a() { }
var a
console.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 = 1
function c(c) {
console.log(c)
var c = 3
}
c(2) // 报错
var可重复声明
function c(c) {
console.log(c) // 2
var c = 5
console.log(c) // 5
}
c(2)
function fn(a){
console.log(typeof a) // "function"
var a
function 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 defined
f1();
console.log("n1=" + n1);
console.log("n2=" + n2);
// console.log("n3="+n3); //Error: n3 is not defined
console.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 = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
var f2 = fn1()
f2()
f2()
// 结果
// 3 4 3 4
说明,每次调用父函数,都生成新的闭包。也可以认为不同的区域,所以f
的闭包与f2
的闭包无关
// 1. 将函数作为另一个函数的返回值
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
function fn3() {
a--
console.log(a)
}
return fn3
}
var f = fn1()
f() // 1
f() // 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 = 2
function fn2 () {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
f = 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,0
var b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2
var 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 = name
this.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)