变量声明提升函数提升
- 通过var定义(声明)的变量,在定义语句之前就可以访问到;值:undefined
- 通过function声明的函数,在声明之前就可以直接调用;值:函数定义(对象)
var a = 3
function fn () {
console.log(a) // undefined 变量提升
var a = 4
/*
相当于
var a ;
console.log(a)
a = 4
*/
}
fn()
console.log(b) // undefined 变量提升
fn2() // fn2() 可调用 函数提升
fn3() // 不可调用 遵循的是变量提升
var b = 3
function fn2(){
console.log('fn2()')
}
var fn3 = function() {
console.log('fn2()')
}
执行上下文
- 代码分类(位置)
- 全局代码
- 函数(局部)代码
- 全局执行上下文
- 在执行全局代码前将window确定为全局执行上下文
- 对全局数据进行预处理
- var定义的全局变量 ==> undefined,添加为window的属性
- function声明的全局函数 ==> 赋值(fun),添加为window的方法
- this==> 赋值(window)
- 开始执行全局代码
// 全局执行上下文
console.log(a1,window.a1) // undefined,undefined
a2() // 'a(2)'
console.log(this) // window
var a1 = 3
function a2(){
console.log('a2')
}
console.log(a1) // 3
- 函数执行上下文
- 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象(虚拟的,存在于栈中)
- 对局部数据进行预处理
- 形参变量 ==> 赋值(实参)==> 添加为执行上下文的属性
- arguments ==> 赋值(实参列表),添加为执行上下文的属性
- var定义的局部变量 ==> undefined,添加为执行上下文的属性
- function声明的函数 ==> 赋值(fun),添加为执行上下文的方法
- this赋值(调用函数的对象)
- 开始执行函数体代码
// 函数执行上下文
function fn(a1){
console.log(a1) // 2
console.log(a2) // undefined
a3() // a(3)
console.log(this) // window
console.log(arguments) // 伪数组(2,3)
var a2 = 3
function a3(){
console.log('a(3)')
}
}
fn(2,3)
执行上下文栈
- 在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象
- 在全局执行上下文(window)确定后,将其添加到栈中(压栈)
- 在函数执行上下文创建后,将其添加到栈中(压栈)
- 在当前函数执行完之后,将栈顶的对象移除(出栈)
- 当所有的代码执行完之后,栈中就只剩下window
// 全局上下文
var a = 10
var bar = function(x) {
var b = 5
foo(x + b) // foo函数的上下文
}
var foo = function(y){
var c = 5
console.log(a + c + y)
}
bar(10) // bar 函数的上下文
/*
先执行变量提升,再执行函数提升
*/
function a(){}
var a
console.log(typeof a) // 'function'
if(!(b in window)){
var b = 1
}
console.log(b) // undefined
var c = 1
function c(c) {
console.log(c)
}
c(2)
/* 相当于
var c
function c(c) {
console.log(c)
}
c = 1
c(2)
*/
This指向规则
this和函数定义的位置没有关系,只和调用者有关系 this是在运行时被绑定的
//定义一个函数
function foo(){
console.log(this) // obj对象
}
//1.调用方式一:直接调用
foo() // window
//2.调用方式二:将foo放到一个对象中,再调用
var obj1 = {
name:'wangjianguo',
foo:foo
}
obj1.foo() // obj对象
//3.调用方式三:通过call或者apply调用
foo.call("wangjianguo") // String{"wangjianguo"} 对象
隐式绑定
通过对象调用函数绑定this
谁直接调用foo()(换而言之,谁离foo()更近),那么foo的this就指向谁。
//obj 调用了foo()方法。因此,this会隐式的被绑定到obj对象上。
function foo(){
console.log(this) // obj对象
}
var obj1 = {
name:'wangjianguo',
foo:foo
}
obj1.foo()
var obj2 = {
name:'wangjianguo',
foo:foo,
obj1:obj
}
// 谁直接调用foo(),那么foo()中的this就指向谁
obj2.obj1.foo()
显式绑定
call函数
function foo(){
console.log(this)
}
foo.call(window) // window
foo.call({name:"wangjianguo"}) // {name:"wangjianguo"}
bind、apply、call的区别
共同点;功能一致
可以改变this指向
语法:函数.call()、函数.apply()、函数.bind()
区别:
- call,apply可以立即执行。bind不会立即执行,因为bind返回的是一个函数需要加入()执行
- 参数不同:apply第二个参数是数组。call和bind有多个参数需要挨个写
var str = '你好'
var obj = {str:'这是obj对象内的str'}
function fun(
Console.log(this, this.str)
)
fun.call(obj) // call立即执行
fun.apply(obj) // apply立即执行
fun.bind(obj) // bind不会立即执行,因为bind返回的是函数
场景
1.用apply的情况
var arr1 = [1,2,4,5,7,3,321]
console.log(Math.max.apply(null,arr1))
2.用bind的情况
var btn = document.getElementById('btn')
var h1s = document.getElementById('h1s')
btn.onClick = function (){
Console.log(this.id)
}.bind(h1s)
使用new关键字绑定this
function Student(name) {
console.log(this) // Student {}
this.name = name // Student {name:"wangjianguo"}
}
var s = new Student("wangjianguo")
console.log(s)
通过new 关键字创建一个新对象的步骤是什么/构造函数是如何创建对象的?
new操作符具体做了什么
- 创建了一个空对象
- 将空对象的隐式原型,指向于构造函数的显式原型
- 将空对象作为构造函数的上下文(改变this指向)
- 对构造函数有返回值的处理判断,如果return的是对象,则直接返回该对象,如果返回的是基本类型,则return语句无效,仍难返回我们创建的对象。
function create(fn,…args){
console.log(args)
// 1.创建了一个空对象
var obj = {}
//2.将空对象的原型,指向于构造函数的原型
Object.setPrototypeOf(obj,fn.prototype)
//3.将空对象作为构造函数的上下文(改变this指向)
var result = fn.apply(obj,args)
//4.对构造函数有返回值的处理判断
return result instanceof Object ? result : obj
}
console.log(create(Fun,18,'zhangsan'))
优先级顺序
- 显式绑定优先级高于隐式绑定
- new绑定优先级高于隐式绑定
- new绑定优先级高于bind
new绑定 > 显式绑定(bind) > 隐式绑定
function foo(){
console.log(this)
}
var ob1 ={
name:'obj1',
foo:foo
}
var ob2 ={
name:'obj2',
foo:foo
}
// 隐式绑定
obj1.foo() // obj1
obj2.foo() // obj2
//隐式绑定和显式绑定同时存在
obj1.foo.call(obj2) // obj2 ,显式绑定优先级更高
把一个函数地址,赋值给另一个对象属性,属于间接引用。
function foo(){
console.log(this)
}
var ob1 ={
name:'obj1',
foo:foo
}
var ob2 ={
name:'obj2'
}
(obj2.foo = obj1.foo)()// window,相当于直接执行foo(),既没隐式绑定,又没显示绑定,就相当于直接使用函数调用,打印出来就是window。
var name = "全局window"
var person = {
name:"person",
sayName:function(){
console.log(this.name)
}
}
function sayName(){
var fun = person.sayName
fun()//全局window
person.sayName()//person
(b = person.sayName)()//全局window
}
sayName()
JS中关于this指向的问题
- 全局对象中的this指向,指向的是window
- 全局作用域或者普通函数中的this,指向全局window
- this永远指向最后调用它的那个对象,在不是箭头函数的情况下
- new 关键字改变了this的指向
- apply,call,bind 可以改变this的指向,不是箭头函数
- 箭头函数中的this 它的指向在定义的时候就已经确定了,箭头函数它没有this,看外层是否有函数,有就是外层函数的this,没有就是window
- 匿名函数中的this,永远指向了window,匿名函数的执行环境具有全局性,因此this指向window
function Foo() {
getName = function(){console.log(1)} // 注意是全局的window
return this
}
Foo.getName = function(){console.log(2)} //
Foo.prototype.getName = function(){console.log(3)} //
var getName = function(){console.log(4)} //
function getName () {
console.log(5)
}
Foo.getName() // 2 在本身去找
getName() // 4 // var getName = function(){console.log(4)} 和 function getName 变量声明的优先级大于普通函数的优先级
Foo().getName() // 执行Foo函数,把var getName = function(){console.log(4)} 替换了 getName = function(){console.log(1)} // 注意是全局的window 打印为1
getName() //当执行getName 去 window上找,getName 函数已经替换,执行函数为1
new Foo.getName() //3 在构造函数上找 ,打印为3
闭包
作用域链
当我们访问一个变量时,JavaScript 引擎首先会在当前作用域寻找这个变量。如果当前作用域没有这个变量,就会去上一层作用域寻找。如果上一层作用域找不到,就去上上层寻找。直到全局作用域都找不到时,返回undefined.
var windowVar = "windowVar"
function outer() {
var outerVar = "outer"
function inner () {
var innerVar = "inner"
console.log(outerVar) // outerVar
console.log(innerVar) // innerVar
console.log(windowVar) // windowVar
}
}
闭包
闭包指的是:即便外部函数已经不存在,也可以获取作用域链上变量的函数。
应用场景:
- 防抖截流
- 库的封装(保证数据的私有性)
function outer(){
const a = 1
function f(){
console.log(a)
}
// 不一定要return 内部函数,才会形成闭包,内部调用也可以
// f()
return f
}
let f = outer()
f() // 1
闭包的优点:
- 内部函数可以访问到外部函数的局部变量
- 闭包可以解决的问题
var lis = document.getElementsByTagName('li')
for (var I =0;i < lis.length;i++){
(function(i){
lis[I].conClick = function(){
alert(i)
}
lis[I] = null
})(i)
}
闭包的缺点:
- 变量会驻留内存中,造成内存损耗问题。解决:把闭包的函数设置为null
- 内存泄露【ie】==》 可说可不说,如果说就一定要提到ie
function fun(){
var element = document.getElementById("button")
console.log(element)
var someResource = new Array(1000).join("*")
element.addEventListener("click",()=>{
console.log(someResource)
})
// 注意 要移除监听
element.removeEventListener("click")
}
fun()
JS作用域考题
- 除了函数外,js 是没有块级作用域
for (var i = 0;i<10;i++){
}
console.log(i) //10
除了函数外,js 是没有块级作用域,相当于声明i声明在外面
var i;
for (i = 0;i<10;i++){}
if (true){
var a = 10
}
console.log(a) // 10
// 相当于
var a
if (true){
a = 10
}
console.log(a) // 10
- 作用域链:内部可以访问外部的变量,但是外部不能访问内部的变量。注意:如果内部有,优先查找内部,如果内部没有就查找外部的
- 注意声明变量是用var还是没有写(没有写就是 window)
(function(){
var a = b = 10
/*
相当于
var a = 10 // a是函数作用域,不能在外部访问。
b = 10,没有写var,声明在全局window
*/
})
// 相当于
var b = 10 //b 声明在全局window
(function(){
var a = 10 //a是函数作用域,不能在外部访问。
})
console.log(a) // error undefined a
console.log(b) // 10
- 注意:js有变量提升的机制【变量悬挂声明】
- 优先级:声明变量 》 声明普通函数 〉 参数 》 变量提升
规则:
- 本层作用域有没有此变量【注意变量提升】
- 注意:js除了函数外没有块级作用域
- 普通声明函数是不看写函数的时候顺序
//普通声明函数是不看写函数的时候顺序
function fun(){
console.log(a) // f a(){}
var a = 10
function a(){}
}
示例
示例1:
function c(){
Var b = 1
function a() {
// var b = undefined 变量提升
console.log(b) // undefined
var b = 2
console.log(b) // 2
}
a()
console.log(b) // 1
}
c()
示例2
var name = 'a'
(function (){
//变量提升 var name = undefined
if (typeof name == 'undefined'){
var name = 'b'
console.log('111' + name)// '111b'
}else {
console.log('222' + name)
}
})()
示例3
function fun(a){
var a =10 // 声明变量 大于 普通函数 大于 参数的优先级
function a(){}
console.log(a) // 10
}
示例4
function fun(){
a = 10 // 相当于在函数外部定义 ,a 的作用域是 window
var a = 20
console.log(a) // 20,本层有,优先用本层的
}
示例5
function fun(){
a = 10
console.log(a) // 10,a 有变量提升,a = 10 相当于再次赋值。
var a = 20
}
示例6
var o = {
a:10,
b:{
fn:function(){
console.log(this.a) // undefined
console.log(this) //b
}
}
}
示例7
window.name = 'ByteDance'
function A(){
this.name = 123
}
A.prototype.getA = function (){
console.log(this)
return this.name + 1
}
let a = new A()
let funcA = a.getA// 相当于把一个函数赋值给了funcA
console.log(funcA()) // funcA在全局作用域下执行,打印 ‘ByteDance1’
示例8
var length = 10
function fn(){
return this.length + 1
}
var obj = {
length:5,
test1:function(){
return fn()
}
}
obj.test2 = fn
console.log(obj.test1()) // 11 这里是一个闭包,返回fn函数, 相当于执行fn(),
console.log(fn() === obj.test2()) // false fn() 执行为11,obj.test2()执行为6
console.log(obj.test1() == obj.test2()) // false obj.test1() 相当于fn(),结果为11,obj.test2() 执行为6