输出题
类型输出
- typeof 之后是''的字符串
 
typeof typeof 1
console.log(typeof typeof 1) ‘string’
typeof NaN   //‘number'
2.包装对象
console.log(Number(2)===2); //true
console.log(Symbol('foo')===Symbol('foo'));  //false
console.log(Boolean(false)===Boolean(false));  //ture
console.log(NaN === NaN); //false
console.log([] === []);  //false
console.log(typeof new String());  //object
console.log(typeof String());   //string
console.log(typeof String);    //function
异步和事件循环
基本顺序
const promise = new Promise((resolve, reject) => {
  console.log(1);
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);
1 
2 
4
promise.then 是微任务,它会在所有的宏任务执行完之后才会执行,同时需要promise内部的状态发生变化,因为这里内部没有发生变化,一直处于pending状态,所以不输出3。
promise状态
const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
  resolve('resolve1')
})
const promise2 = promise1.then(res => {
  console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);
promise1
1 Promise{<resolved>: resolve1}
2 Promise{<pending>}
resolve1
直接打印promise1,会打印出它的状态值和参数
代码执行过程如下:
- script是一个宏任务,按照顺序执行这些代码;
 - 首先进入Promise,执行该构造函数中的代码,打印
promise1; - 碰到
resolve函数, 将promise1的状态改变为resolved, 并将结果保存下来; - 碰到
promise1.then这个微任务,将它放入微任务队列; promise2是一个新的状态为pending的Promise;- 执行同步代码1, 同时打印出
promise1的状态是resolved; - 执行同步代码2,同时打印出
promise2的状态是pending; - 宏任务执行完毕,查找微任务队列,发现
promise1.then这个微任务且状态为resolved,执行它。 
promsie和setTimeout
setTimeout(() => {
  console.log(7)
}, 0)
new Promise((resolve, reject) => {
  console.log(3);
  resolve();
  console.log(4);
}).then(() => {
  console.log(6)
})
console.log(5) 
3
4
5
6
7
promise.then()返回新的promsie
setTimeout(() => {
  console.log(7)
}, 0)
new Promise((resolve, reject) => {
  console.log(3);
  resolve();
  console.log(4);
}).then(() => {
  console.log(6)
}).then(() => {
  console.log(8);
}).then(() => {
  console.log(9);
})
console.log(5)
3
4
5
6
8
9
7
promise套promise
new Promise(resolve => {
  console.log('Promise1');
  resolve()
}).then(()=>{
  console.log('then11')
  new Promise(resolve => {
    console.log('Promise2');
    resolve()
  }).then(()=>{
    console.log('then21');
  }).then(()=>{
    console.log('then23');
  })
}).then(()=>{
    console.log('then12');
}) 
Promise1
then11
Promise2
then21
then12
then23
then21和then12属于一轮
promise套setTimeout
const promise = new Promise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    console.log("timerStart");
    resolve("success");
    console.log("timerEnd");
  }, 0);
  console.log(2);
});
promise.then((res) => {
  console.log(res);
});
console.log(4);
1
2
4
timerStart
timerEnd
success
代码执行过程如下:
- 首先遇到
Promise构造函数,会先执行里面的内容,打印1; - 遇到定时器
steTimeout,它是一个宏任务,放入宏任务队列; - 继续向下执行,打印出2;
 - 由于
Promise的状态此时还是pending,所以promise.then先不执行; - 继续执行下面的同步任务,打印出4;
 - 此时微任务队列没有任务,继续执行下一轮宏任务,执行
steTimeout; - 首先执行
timerStart,然后遇到了resolve,将promise的状态改为resolved且保存结果并将之前的promise.then推入微任务队列,再执行timerEnd; - 执行完这个宏任务,就去执行微任务
promise.then,打印出resolve的结果。 
setTimeout套promise
Promise.resolve().then(() => {
  console.log('promise1');
  const timer2 = setTimeout(() => {
    console.log('timer2')
  }, 0)
});
const timer1 = setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('start');
start
promise1
timer1
promise2
timer2
代码执行过程如下:
- 首先,
Promise.resolve().then是一个微任务,加入微任务队列 - 执行timer1,它是一个宏任务,加入宏任务队列
 - 继续执行下面的同步代码,打印出
start - 这样第一轮宏任务就执行完了,开始执行微任务
Promise.resolve().then,打印出promise1 - 遇到
timer2,它是一个宏任务,将其加入宏任务队列,此时宏任务队列有两个任务,分别是timer1、timer2; - 这样第一轮微任务就执行完了,开始执行第二轮宏任务,首先执行定时器
timer1,打印timer1; - 遇到
Promise.resolve().then,它是一个微任务,加入微任务队列 - 开始执行微任务队列中的任务,打印
promise2; - 最后执行宏任务
timer2定时器,打印出timer2; 
(() => {
  setTimeout(() => {
    console.log("1-1");
    Promise.resolve().then(() => {
      console.log("1-2");
    });
  });
  console.log("2-1");
  Promise.resolve().then(() => {
    console.log("3-1");
    setTimeout(() => {
      console.log("3-2");
    });
  });
  new Promise(function (reslove) {
    console.log('4-1');
    reslove();
  }).then(function () {
    console.log('4-2');
  })
})()  
2-1
4-1
3-1
4-2
1-1
1-2
3-2
状态改变
const promise = new Promise((resolve, reject) => {
    resolve('success1');
    reject('error');
    resolve('success2');
});
promise.then((res) => {
    console.log('then:', res);
}).catch((err) => {
    console.log('catch:', err);
})
then:success1
Promise的状态在发生变化之后,就不会再发生变化。开始状态由pending变为resolve,说明已经变为已完成状态,下面的两个状态的就不会再执行,同时下面的catch也不会捕获到错误。
const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
})
const promise2 = promise1.then(() => {
  throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
  console.log('promise1', promise1)
  console.log('promise2', promise2)
}, 2000)
promise1 Promise {<pending>}
promise2 Promise {<pending>}
Uncaught (in promise) Error: error!!!
promise1 Promise {<fulfilled>: "success"}
promise2 Promise {<rejected>: Error: error!!}
只有执行了resolve()或者reject()状态才会发生改变
Promise.reject('err!!!')
  .then((res) => {
    console.log('success', res)
  }, (err) => {
    console.log('error', err)
  }).catch(err => {
    console.log('catch', err)
  })
error err!!!
.then函数中的两个参数:
- 第一个参数是用来处理Promise成功的函数
 - 第二个则是处理失败的函数
 
也就是说Promise.resolve('1')的值会进入成功的函数,Promise.reject('2')的值会进入失败的函数。
在这道题中,错误直接被then的第二个参数捕获了,所以就不会被catch捕获了,输出结果为:error err!!!'
链式调用传值
Promise.resolve(1)
  .then(res => {
    console.log(res);
    return 2;
  })
  .catch(err => {
    return 3;
  })
  .then(res => {
    console.log(res);
  });
1   
2
Promise是可以链式调用的,由于每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用, 它并不像一般任务的链式调用一样return this。
上面的输出结果之所以依次打印出1和2,是因为resolve(1)之后走的是第一个then方法,并没有进catch里,所以第二个then中的res得到的实际上是第一个then的返回值。并且return 2会被包装成resolve(2),被最后的then打印输出2
Promise.resolve(1)
  .then(res => {
    console.log(res);
    throw err
  })
  .catch(err => {
    return 3;
  })
  .then(res => {
    console.log(res);
  });
1
3
上面的输出结果之所以依次打印出1和3,是因为resolve(1)之后走的是第一个then方法,抛出错误进catch里,所以第二个then中的res得到的实际上是第catch的返回值。并且return 3会被包装成reject(3),被最后的then打印输出3
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)
1
Promise {<fulfilled>: undefined}
Promise.resolve方法的参数如果是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的Promise对象,状态为resolved,Promise.resolve方法的参数,会同时传给回调函数
then方法接受的参数是函数,而如果传递的并非是一个函数,它实际上会将其解释为then(null),这就会导致前一个Promise的结果会传递下面
好多的then,实际上只需要记住一个原则:.then 或.catch 的参数期望是函数,传入非函数则会发生值透传
第一个then和第二个then中传入的都不是函数,一个是数字,一个是对象,因此发生了透传,将resolve(1) 的值直接传到最后一个then里,直接打印出1
Promise.resolve().then(() => {
  return new Error('error!!!')
}).then(res => {
  console.log("then: ", res)
}).catch(err => {
  console.log("catch: ", err)
})
"then: " "Error: error!!!"
返回任意一个非 promise 的值都会被包裹成 promise 对象,因此这里的return new Error('error!!!')也被包裹成了return Promise.resolve(new Error('error!!!')),因此它会被then捕获而不是catch。
const promise = Promise.resolve().then(() => {
  return promise;
})
promise.catch(console.err)
Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。
finally
Promise.resolve('1')
  .then(res => {
    console.log(res)
  })
  .finally(() => {
    console.log('finally')
  })
Promise.resolve('2')
  .finally(() => {
    console.log('finally2')
    return '我是finally2返回的值'
  })
  .then(res => {
    console.log('finally2后面的then函数', res)
  })
1
finally2
finally
finally2后面的then函数 2
.finally()一般用的很少,只要记住以下几点就可以了:
.finally()方法不管Promise对象最后的状态如何都会执行.finally()方法的回调函数不接受任何的参数,也就是说你在.finally()函数中是无法知道Promise最终的状态是resolved还是rejected的- 它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。
 - finally本质上是then方法的特例
 
Promise.resolve('1')
  .finally(() => {
    console.log('finally1')
    throw new Error('我是finally中抛出的异常')
  })
  .then(res => {
    console.log('finally后面的then函数', res)
  })
  .catch(err => {
    console.log('捕获错误', err)
  })
'finally1'  
'捕获错误'    Error: 我是finally中抛出的异常
var p = new Promise(resolve => {
    console.log(4);
    resolve(5);
});
function func1() {
    console.log(1);
    function func2() {
        setTimeout(() => {
            console.log(2);
        });
    }
    func2();
}
func1();
console.log(3);
p.then(resolved => {
    console.log(resolved)
}
).then(() => {
    console.log(6)
});
this指向
简单函数调用
function show(){
    console.log(this);
}
show() //Window{}
var obj={
    show:function show(){
        console.log(this);
}
}
obj.show()//obj{}
对象内函数调用函数
function foo() {
  console.log( this.a );
}
function doFoo() {
  foo();
}
var obj = {
  a: 2,
  doFoo: doFoo
};
var a = 1; 
obj.doFoo()
//1
在Javascript中,this指向函数执行时的当前对象。在执行foo的时候,执行环境就是doFoo函数,执行环境为全局。所以,foo中的this是指向window的,所以会打印出1。
var name='1';
var obj={
    name:'2',
    fun1L:function(){
            function func2(){
                console.log(this.name);
            }
            func2()
    }
}
obj.fun1L()//1
对象内函数返回函数
 var obj={
    show:function show(){
       return   function show2(){
            console.log(this);
        }
        
}
}
obj.show()() //window{}
var show3=obj.show()
show3()   //window{}
 var obj={
    show:function show(){
       return()=>{
            console.log(this);
        }
        
}
}
obj.show()() //obj{}
var show3=obj.show()
show3()   //obj{}
对象内箭头函数调用
var obj = {
   say: function() {
     var f1 = () =>  {
       console.log("1111", this);
     }
     f1();
   },
   pro: {
     getPro:() =>  {
        console.log(this);
     }
   }
}
var fn = obj.say;
fn();
obj.say();
obj.pro.getPro();
//1111 window{}
//1111 obj{}
//window{}
- fn(),fn是在全局执行的,而f1是箭头函数,它是没有绑定this的,它的this指向其父级的this,其父级say方法的this指向的是全局作用域,所以会打印出window;
 - obj.say(),谁调用say,say 的this就指向谁,所以此时this指向的是obj对象;
 - obj.pro.getPro(),我们知道,箭头函数时不绑定this的,getPro处于pro中,而对象不构成单独的作用域,所以箭头的函数的this就指向了全局作用域window。
 
对象内普通函数和箭头函数
var a = 10
var obj = {
  a: 20,
  say: () => {
    console.log(this.a)
  }
}
obj.say() 
var anotherObj = { a: 30 } 
obj.say.apply(anotherObj) 
// 10  10
我么知道,箭头函数时不绑定this的,它的this来自原其父级所处的上下文,所以首先会打印全局中的 a 的值10。后面虽然让say方法指向了另外一个对象,但是仍不能改变箭头函数的特性,它的this仍然是指向全局的,所以依旧会输出10。
如果是普通函数,那么就会有完全不一样的结果:
var a = 10  
var obj = {  
  a: 20,  
  say(){
    console.log(this.a)  
  }  
}  
obj.say()   
var anotherObj={a:30}   
obj.say.apply(anotherObj)
//20 30
say方法中的this就会指向他所在的对象,输出其中的a的值
call绑定null
function a() {
  console.log(this);
}
a.call(null);
//Window{}
如果第一个参数传入的对象调用者是null或者undefined,call方法将把全局对象(浏览器上是window对象)作为this的值。所以,不管传入null 还是 undefined,其this都是全局对象window。所以,在浏览器上答案是输出 window 对象。
要注意的是,在严格模式中,null 就是 null,undefined 就是 undefined
'use strict';
function a() {
    console.log(this);
}
a.call(null); // null
a.call(undefined); // undefined
new构造函数
var obj = { 
  name : 'cuggz', 
  fun : function(){ 
    console.log(this.name); 
  } 
} 
obj.fun()     // cuggz
new obj.fun() // undefined
使用new构造函数时,其this指向的是全局环境window。
function foo(something){
    this.a = something
}
var obj1 = {
    foo: foo
}
var obj2 = {}
obj1.foo(2); 
console.log(obj1.a); // 2
obj1.foo.call(obj2, 3);
console.log(obj2.a); // 3
var bar = new obj1.foo(4)
console.log(obj1.a); // 2
console.log(bar.a); // 4
- 首先执行obj1.foo(2); 会在obj中添加a属性,其值为2。之后执行obj1.a,a是右obj1调用的,所以this指向obj,打印出2;
 - 执行 obj1.foo.call(obj2, 3) 时,会将foo的this指向obj2,后面就和上面一样了,所以会打印出3;
 - obj1.a会打印出2;
 - 最后就是考察this绑定的优先级了,new 绑定是比隐式绑定优先级高,所以会输出4。
 
function foo(something){
    this.a = something
}
var obj1 = {}
var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2
var baz = new bar(3);
console.log(obj1.a); // 2
console.log(baz.a); // 3
主要都是考察this绑定的优先级。记住以下结论即可:**this绑定的优先级:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
对象内的立即执行函数
var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log(this.foo);  //bar
        console.log(self.foo);  //bar
        (function() {
            console.log(this.foo);   //undefined
            console.log(self.foo);  //bar
        }());
    }
};
myObject.func();
//bar bar undefined bar
- 首先func是由myObject调用的,this指向myObject。又因为var self = this;所以self指向myObject。
 - 这个立即执行匿名函数表达式是由window调用的,this指向window 。立即执行匿名函数的作用域处于myObject.func的作用域中,在这个作用域找不到self变量,沿着作用域链向上查找self变量,找到了指向 myObject对象的self。
 
window.number = 2;
var obj = {
 number: 3,
 db1: (function(){
   console.log(this);
   this.number *= 4;
   return function(){
     console.log(this);
     this.number *= 5;
   }
 })()
}
var db1 = obj.db1;
db1();
obj.db1();
console.log(obj.number);     // 15
console.log(window.number);  // 40
- 执行db1()时,this指向全局作用域,所以window.number 4 = 8,然后执行匿名函数, 所以window.number 5 = 40;
 - 执行obj.db1();时,this指向obj对象,执行匿名函数,所以obj.numer * 5 = 15。
 
var x = 3;
var y = 4;
var obj = {
    x: 1,
    y: 6,
    getX: function() {
        var x = 5;
        return function() {
            return this.x;
        }();
    },
    getY: function() {
        var y = 7;
        return this.y;
    }
}
console.log(obj.getX()) // 3
console.log(obj.getY()) // 6
- 我们知道,匿名函数的this是指向全局对象的,所以this指向window,会打印出3;
 - getY是由obj调用的,所以其this指向的是obj对象,会打印出6。
 
函数当参数传递
var length = 10;
function fn() {
    console.log(this.length);
}
 
var obj = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};
 
obj.method(fn, 1);
//10 2
- 第一次执行fn(),this指向window对象,输出10。
 - 第二次执行arguments[0],相当于arguments调用方法,this指向arguments,而这里传了两个参数,故输出arguments长度为2。
 
var a = 1;
function printA(){
  console.log(this.a);
}
var obj={
  a:2,
  foo:printA,
  bar:function(){
    printA();
  }
}
obj.foo(); // 2
obj.bar(); // 1
var foo = obj.foo;
foo(); // 1
- obj.foo(),foo 的this指向obj对象,所以a会输出2;
 - obj.bar(),printA在bar方法中执行,所以此时printA的this指向的是window,所以会输出1;
 - foo(),foo是在全局对象中执行的,所以其this指向的是window,所以会输出1;
 
重复声明
 var a = 10; 
 var obt = { 
   a: 20, 
   fn: function(){ 
     var a = 30; 
     console.log(this.a)
   } 
 }
 obt.fn();  // 20
 obt.fn.call(); // 10
 (obt.fn)(); // 20
- obt.fn(),fn是由obt调用的,所以其this指向obt对象,会打印出20;
 - obt.fn.call(),这里call的参数啥都没写,就表示null,我们知道如果call的参数为undefined或null,那么this就会指向全局对象this,所以会打印出 10;
 - (obt.fn)(), 这里给表达式加了括号,而括号的作用是改变表达式的运算顺序,而在这里加与不加括号并无影响;相当于 obt.fn(),所以会打印出 20;
 
this返回
function a(xx){
  this.x = xx;
  return this
};
var x = a(5);
var y = a(6);
console.log(x.x)  // undefined
console.log(y.x)  // 6
- 最关键的就是var x = a(5),函数a是在全局作用域调用,所以函数内部的this指向window对象。所以 this.x = 5 就相当于:window.x = 5。之后 return this,也就是说 var x = a(5) 中的x变量的值是window,这里的x将函数内部的x的值覆盖了。然后执行console.log(x.x), 也就是console.log(window.x),而window对象中没有x属性,所以会输出undefined。
 - 当指向y.x时,会给全局变量中的x赋值为6,所以会打印出6。
 
作用域和变量提升
不使用var let const
说明: 1.使用var声明变量,在方法内部是局部变量,在方法外部是全局变量 2.没有使用var声明的变量,在方法内部或外部都是全局变量,但如果是在方 法内部声明,在方法外部使用之前需要先调用方法,告知系统声明了全局变量后方可在方法外部使用。
在函数作用域内 加var定义的变量是局部变量,不加var定义的就成了全局变量 在function内部,加var的是局部变量,不加var的则是全局变量; 在function外部,不管有没有使用var声明变量,都是全局变量,在function外部,var关键字一般可以省略,但是为了书写规范和维护方便以及可读性好,不建议省略var关键字
var常见问题
允许重复声明,导致变量被覆盖
var a=1;
var a=2;
var a=3;
console.log(a); //3
var a = 1;
function prinf(){
    console.log(a);
}
var a = 2;
prinf();//2
这里假设你var a = 2前面还有上万行代码,那么你调用prinf函数的时候,是想打印之前的a,但是你忘记之前声明的变量和几万行代码后面的a是同名的,导致后面声明的a覆盖了之前的a.
console.log(a)  //undefined
var a=1;
console.log(a) //1
var a=2
console.log(a)  //2
特殊情况
var a = 1;
function prinf(){
    console.log(a);
    var a = 2;
}
prinf();//undefined
let a = 1;
function prinf(){
    console.log(a);
    let a = 2;
}
prinf();//Identifier 'a' has already been declared
全局变量挂载到全局对象:全局对象成员污染问题
var a=1;
console.log(window.a); //1
var console = "打印";
console.log(console);
//console.log is not a function
let使用
不能重复声明
let a=1;
var a=2;
console.log(a)  //SyntaxError: Identifier 'a' has already been declared
初始化问题(先声明,再赋值) 报错
a=1;
let a=2;
console.log(a)//Uncaught ReferenceError: Cannot access 'a' before initialization
let a=1;
a=2;
console.log(a) //2
基本原理
1.外层的变量函数内部可以找到,函数内部的变量(局部变量)外层找不到。
由于a是函数内部定义的局部变量,根据作用域关系,外层(全局环境)访问不到局部变量。因此会报错
function aaa() {
        var a = 10;
}
alert(a);//Error a is not defined  
var a=10;
    function aaa(){
        alert(a);
    }
    
    function bbb(){
        var a=20;
        aaa();
    }
    bbb();//10  由于aaa()执行时aaa的作用域链中a=10;
2.当var不加时,会自动生成全局变量(不建议这样写,一般定义变量时建议使用var关键字)
function aaa() {
    var a = b = 10;//b未用var定义相当于全局变量,所以在函数外能够被访问到
}
aaa();
//alert(a);//会报错,a is not defined
alert(b);//10    相当于全局变量可以访问
 //此时上面代码相当于以下代码
 var b;
 function aaa() {
     b = 10;
     var a = b;
 }
 aaa();
 alert(a);//Error
 alert(b);//10
3.变量的查找是就近原则,找就近var定义的,就近找不到的话则在外层寻找。
在内部找到了a 虽然是undefine
var a=10;
    function aaa(){
        alert(a);
        var a=20;
    }
    aaa();//undefined   就近原则找到var定义的,预解析过程
4.当参数和局部变量重名时,优先级等同
var a=10;
function aaa(a){
      alert(a);
       var a=20;
}
aaa(a);//10  由于参数和局部变量优先级等同,所以查找时找到局部变量10然后再是var定义的局部变量
局部变量将参数覆盖
var a=10;
    function aaa(a){
        var a=20;
        alert(a);
    }
    aaa(a);//20  局部变量将参数覆盖了,因此a值为局部变量的值
基本类型
var a=10;
    function aaa(a){
        a+=3;//基本类型 /参数相当于局部变量,只是局部修改了a的值,而在全局访问时依然是全局的值
    }
    aaa(a);
    alert(a);//10 
引用类型是引用的地址
var a=[1,2,3];var b=[1,2,3];
    function aaa(a,b){
        a.push(4);     b=[1,2,3,4];
    }
    aaa(a,b);
    alert(a);//[1,2,3,4]  引用类型是引用的地址,所以局部修改会影响全局的值    alert(b);//[1,2,3]
如果一个变量名没有被声明过,且没有使用 var 就赋予了初始值,它就会成为全局作用域下的变量。
function setA() {
  a = 1;
}
setA();
console.log(a); // 1
如果多次声明同一个变量, 之后的 var 声明会被忽略掉。
var a = 1;
var a = 2;
var a = 3;
console.log(a)
//3
等价于
var a = 1;
a = 2;
a = 3;
console.log(a)
//3
函数声明你也可以认为是一个 var 声明,千万不要以为后面的 var 声明能够覆盖掉同名的函数声明。
变量提升问题
变量声明和函数声明都是会发生 提升 (hoist)的。
怎么理解,就是在执行代码前,会先扫描一下代码,将其中类似 var a = 1 的语句中声明的部分,也就是 var a 先找出来,先执行。
这是编译器的行为,所以在表现上,就是声明被放到了代码的开头,成为 提升。
除了 var / let / const 声明的变量会提升,函数声明也会提升,且函数声明会优先于变量提升,即放到变量声明的上方。
b();
var a = 1;
function b() { console.log(a) };
等价于
function b() { console.log(a) };
var a;
b(); // 输出 undefined
a = 1;
var a=10;
function fn(){
    console.log(a) //暂时性死区  变量提升
    var a=20
}
fn()//undefined
有一个易错点,就是把函数表达式当成了函数声明。函数表达式是不会发生变量提升的,切记
function fn(a) {
  console.log(a);
  var a = 2;
  function a() { }
  console.log(a);
}
fn(1);
首先是函数的参数 a,它等价于一个外部的一个变量,上面代码等价于:
var a = 1;
(function() {
  function a() { }
  
  console.log(a);
  a = 2;
  console.log(a);
})();
这里函数声明做了提升,跑到最顶部。接下来考虑给 var a = 2 做提升。但我们的函数 a 的声明已经提升了,所以这里的 var 算是重复声明了,直接去掉 var,最终得到上述代码。
我们很容易看出答案是:
ƒ a() { }
2
本题除了考察比较常规的声明提升,还考察了一个比较特别的情况,就是 函数的参数声明应该放在哪里?
答案是放到一个额外的包裹着函数体的中间层中,而不是直接放到函数体的顶部。
var a=8;
function foo(){
  var a=6
  return function(){
    a=5
  }
}
foo()()
console.log(a)//8
作用域题目
立即执行函数作用域
(function (args) {
    statement;
})(args)
- 该函数表达式在定义时就会调用
 - 该函数将拥有自己独立的词法作用域,外部无法访问
 - 该函数可以访问到外部的变量
 
var a = 0; // 可以被立即执行函数访问
(function () {
    console.log(a); // 0
    var b = 1; // 不能被外部访问
})()
console.log(b); // ReferenceError: b is not defined
var a = 0; // 可以被立即执行函数访问
(function () {
    console.log(a); // 0
     b = 1; // 能被外部访问
})()
console.log(b); // 1
var a = 10;
(function(){
var a;
console.log(window.a)
console.log(a)
a=20
console.log(a)
})()
//10
//undefined
//20
//undefined
对象嵌套
var b = {
a,
c: b
}
console.log(b)
{
    "a": 10,
    "c": {
        "a": 10,
        "c": {
            "a": 10
        }
    }
}

1.var循环中的setTimeout问题
for(var i = 0; i < 5; i++){
setTimeout(()=>{
console.log(i);
},1000)
}
5,5,5,5,5
因为setTimeout是异步执行,每一次for循环的时候,setTimeout都执行一次,但是里面的函数没有被执行,而是被放到了任务队列里,等待执行。只有主线上的任务执行完,才会执行任务队列里的任务。也就是说它会等到for循环全部运行完毕后,才会执行fun函数,但是当for循环结束后此时i的值已经变成了5,因此虽然定时器跑了5秒,控制台上的内容依然是5
用闭包方式改写
for (var i = 0; i < 5; i++) {
(function (i) {
setTimeout(() => {
console.log(i);
}, 1000)
})(i);
}
for (var i = 1; i <= 5; i++) {
   setTimeout(
       function timer(j) {
           console.log(j)
       },
       i * 1000,
       i
   )
}
var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i); // i 指向全局的 i,也就是数组中函数所有的i都指向的是同一个变量i
  };
}
a[6](); // 10
在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会自增,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是循环结束之后i的值,也就是 10。
如果使用let,声明的变量仅在块级作用域内有效,最后输出的是 6
这是因为let 创建的变量是块级作用域的,所以每次循环都是一个新的 i,每次的值都是 i++ 的结果。因为每次循环的 i 都是一个独立的变量(内存里的唯一地址),因此闭包记录的值都是唯一的,所以才能得到最终的结果
如果用 var 的话,变量 i 是一个全局变量,虽然循环体内每次都创建了一个函数来打印 i,但是当时当刻仅仅是一个指向全局变量 i 的指针,当循环结束之后无论你用哪一个下标去访问循环创建的闭包函数,打印的变量 i 都是全局的那一个,所以全部都是 10。
2.立即执行函数作用域
(function(){
   var x = y = 1;
})();
var z;
console.log(y); // 1
console.log(z); // undefined
console.log(x); // Uncaught ReferenceError: x is not defined
这段代码的关键在于:var x = y = 1; 实际上这里是从右往左执行的,首先执行y = 1, 因为y没有使用var声明,所以它是一个全局变量,然后第二步是将y赋值给x,讲一个全局变量赋值给了一个局部变量,最终,x是一个局部变量,y是一个全局变量,所以打印x是报错。
var a = 100;
function create(){
var a = 200;
return function(){
console.log(a + 'create');
}
}
var fn2 = create();
fn2();
//200 create
3.函数和变量同名
console.log(x);
x()
var x=2;
function x(){
    console.log(1);
}
console.log(x);
x=9;
x()
输出结果
ƒ x(){
    console.log(1);
}
 1
 2
 Uncaught TypeError: x is not a function
5.作用域和this
let obj={
    num:117
}
var num=935;
function func(){
    console.log(this.num);
}
const wrapper=func.bind(obj)
setTimeout(() => {
        wrapper()
}, 1000);
obj={
    num:130
}
//117
原型继承
1.原型相等
function func1(){}
function func2(){}
func1.prototype=func2.prototype={}
const son=new func1()
console.log(son instanceof func1,son instanceof func2);//true true
2.箭头函数没有prototype
function giveLydiaPizza() {
  return "Here is pizza!"
}
const giveLydiaChocolate = () => "Here's chocolate... now go hit the gym already."
console.log(giveLydiaPizza.prototype) //{ constructor: ...}
console.log(giveLydiaChocolate.prototype) //undefined
例如giveLydiaPizza函数,有一个prototype属性,它是一个带有constructor属性的对象(原型对象)。 然而,箭头函数,例如giveLydiaChocolate函数,没有这个prototype属性。 尝试使用giveLydiaChocolate.prototype访问prototype属性时会返回undefined
class Person {
  constructor() {
    this.name = "Lydia"
  }
}
Person = class AnotherPerson {
  constructor() {
    this.name = "Sarah"
  }
}
const member = new Person()
console.log(member.name)
//"Sarah"
我们可以将类设置为等于其他类/函数构造函数。 在这种情况下,我们将Person设置为AnotherPerson。 这个构造函数的名字是Sarah,所以新的Person实例member上的 name 属性是Sarah。
其他
1.对象的symbol属性
const info = {
  [Symbol('a')]: 'b'
}
console.log(info)
console.log(Object.keys(info))
// {Symbol('a'): 'b'}  []
Symbol类型是不可枚举的。Object.keys方法返回对象上的所有可枚举的键属性。Symbol类型是不可见的,并返回一个空数组。 记录整个对象时,所有属性都是可见的,甚至是不可枚举的属性。
这是Symbol的众多特性之一:除了表示完全唯一的值(防止对象意外名称冲突,例如当使用 2 个想要向同一对象添加属性的库时),您还可以隐藏这种方式对象的属性(尽管不完全。你仍然可以使用Object.getOwnPropertySymbols()方法访问 Symbol。
2.剩余参数
function getItems(fruitList, ...args, favoriteFruit) {
  return [...fruitList, ...args, favoriteFruit]
}
getItems(["banana", "apple"], "pear", "orange") //SyntaxError
... args是剩余参数,剩余参数的值是一个包含所有剩余参数的数组,并且只能作为最后一个参数。上述示例中,剩余参数是第二个参数,这是不可能的,并会抛出语法错误。
function getItems(fruitList, favoriteFruit, ...args) {
  return [...fruitList, ...args, favoriteFruit]
}
getItems(["banana", "apple"], "pear", "orange")
上述例子是有效的,将会返回数组:[ 'banana', 'apple', 'orange', 'pear' ]
3.push方法返回的是数组长度
let newList = [1, 2, 3].push(4)
console.log(newList.push(5))  //TypeError
.push方法返回数组的长度,而不是数组本身! 通过将newList设置为[1,2,3].push(4),实际上newList等于数组的新长度:4。
然后,尝试在newList上使用.push方法。 由于newList是数值4,抛出 TypeError。
4.默认传参
function test(m = n, n = 2) {
    console.log(m, n)
}
test() // Uncaught ReferenceError: Cannot access 'n' before initialization
test(3) // 3 2
test(3, 4) // 3 4
5.[].map(parseInt)
var arr = ['1', '2', '3'];
var r;
r = arr.map(parseInt);
console.log(r); //1, NaN, NaN
概念:以第二个参数为基数来解析第一个参数字符串,通常用来做十进制的向上取整(省略小数)如:parseInt(2.7) //结果为2
特点:接收两个参数parseInt(string,radix)
string:字母(大小写均可)、数组、特殊字符(不可放在开头,特殊字符及特殊字符后面的内容不做解析)的任意字符串,如 '2'、'2w'、'2!'
radix:解析字符串的基数,基数规则如下:
1) 区间范围介于2~36之间,否则返回NaN
2 ) 当参数为 0,parseInt() 会根据十进制来解析;
3 ) 如果忽略该参数,默认的基数规则:
 如果 string 以 "0x" 开头,parseInt() 会把 string 的其余部分解析为十六进制的整数;parseInt("0xf") //15
 如果 string 以 0 开头,其后的字符解析为八进制或十六进制的数字;parseInt("08") //8
 如果 string 以 1 ~ 9 的数字开头,parseInt() 将把它解析为十进制的整数;parseInt("88.99f") //88
 只有字符串中的第一个数字会被返回。parseInt("10.33") //返回10;
 开头和结尾的空格是允许的。parseInt(" 69 10 ") //返回69
 如果字符串的第一个字符不能被转换为数字,返回 NaN。parseInt("f") //返回NaN 而parseInt("f",16) //返回15
map 函数将数组的值 value 传递给了 parseInt 的第一个参数,将数组的索引传递给了第二个参数。 第三个参数呢?我们再加一个参数。
a = ["1","2", "3"]
var parseInt = function(string, radix, obj) {
    return string + "-" + radix + "-" + obj;
};
console.log(a.map(parseInt)); //[ '1-0-1,2,3', '2-1-1,2,3', '3-2-1,2,3' ]
可以看出,map 确实为 parseInt 传递了三个参数 数组的值,值对应的索引,原数组。
可以看出,map的回调函数的参数index索引值作了parseInt的基数radix,索引index的起始值从0开始。
得出,正是由于map的回调函数的参数index索引值作了parseInt的基数radix,导致出现超范围的radix赋值和不合法的进制解析,才会返回NaN。
["1","2", "3"].map(parseInt)
[0]=parseInt("1",0);//十进制1
[1]=parseInt("2",1);//radix不在2~36的返回NaN
[2]=parseInt("3",2);//二进制数没有3,只有0 1 ,所以NaN