输出题
类型输出
- 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