目录
原型
Function与Object
new fn()
原型链
constructor
function.length
默认参数:第一个具有默认值之前的参数个数
剩余参数:不算进length
闭包
循环中
函数工厂:形参传递
IIFE:匿名闭包
let:闭包
forEach():和let+for类似
setTimeout
IIFE:匿名闭包
setTimeout(functionRef, delay, param...)
this+闭包
this
JS预解析/编译(变量提升)
let
暂时性死区
=优先级:从右到左
输出顺序Event loop
async、await事件轮询执行时机
async隐式返回Promise,会产生一个微任务await xx;后的代码在微任务时执行
Node中的process.nextTick
process.nextTick执行顺序早于微任务
string
str[i]=赋值
str.indexOf('',i):i
n=n++
缓存原值,自增,用缓存的原值进行运算
从左到右解析,能组成符号就组
var F = function() {};
Object.prototype.a = function() {
console.log('a');
};
Function.prototype.b = function() {
console.log('b');
}
var f = new F();
f.a(); // a
f.b(); // f.b is not a function
F.a(); // a
F.b(); // b
function A() {}
function B(a) {
this.a = a;
}
function C(a) {
if (a) {
this.a = a;
}
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;
console.log(new A().a); //1
console.log(new B().a); //undefined(传入a为undefined)
console.log(new C(2).a);//2
console.log(new C().a); //1
原型链
123['toString']:在数字 123 上使用方括号访问属性
数字本身没有toString
方法,则沿着__proto__
去function Number()
的prototype
上找,找到toString方法,toString方法的length是1
numObj.toString([radix])
console.log(123['toString'].length + 123) // 124
function fun(){
this.a = 0
this.b = function(){
console.log("自己的b:",this.a)
}
}
fun.prototype = {
b: function(){
this.a = 20
console.log("原型链b:",this.a)
},
c: function (){
this.a = 30
console.log(this.a)
}
}
var my_fun = new fun()
my_fun.b() // 0
my_fun.c() // 30
function Foo() {
getName = function (){
console.log(1)
}
return this
}
Foo.getName = function () {
console.log(2)
}
Foo.prototype.getName = function(){
console.log(3)
}
Foo.getName()//2
Foo().getName();//1
getName();//1:getName函数变量提升到全局
new Foo.getName()//2 Foo函数有对象有个getName(...2)属性方法
//先对 new Foo() 实例化再对 A.getName() 调用,
//对 new Foo() 实例化调用的 getName() 方法是原型 prototype 上的
new Foo().getName()//3
// new new Foo().getName() => new B.getName(),
//先对 new Foo() 实例化再对 new B.getName() 实例化,
//new B.getName() 同时也在执行 B.getName() 方法输出的还是实例 B 上的方法也就是原型 prototype 的 getName
new new Foo().getName()//3
f1,f2中本没有 constructor
但是会从构造函数的 prototype
中查找相当f1.prototype.constructor
,f2
的原型被重新定义了指向基类 object
找不到的,只会往上找,而非往下,所以原型上不存在n
function Fn(){
var n = 10
this.m = 20
this.aa = function() {
console.log(this.m)
}
}
Fn.prototype.bb = function () {
console.log("原型的this.n",this.n)
}
var f1 = new Fn
Fn.prototype = {
aa: function(){
console.log(this.m + 10)
}
}
var f2 = new Fn
//注意区别修改原型Fn.prototype和修改原型的属性Fn.prototype.bb
console.log(f1.constructor) // ==> function Fn(){...}
console.log(f2.constructor) // ==> Object() { [native code] }
//原型中
f1.bb() // n是 undefined
//自己有aa方法,就不去原型链上找了
f1.aa() // 20
f2.aa() // 20
//原型链上的aa方法中,原型没有m属性,undefined+10=NaN
f2.__proto__.aa() // NaN
f2.bb() // Uncaught TypeError: f2.bb is not a function
默认参数:第一个具有默认值之前的参数个数
function fn1 (name) {}
function fn2 (name = '林三心') {}
function fn3 (name, age = 22) {}
function fn4 (name, age = 22, gender) {}
function fn5(name = '林三心', age, gender) { }
console.log(fn1.length) // 1
console.log(fn2.length) // 0
console.log(fn3.length) // 1
console.log(fn4.length) // 1
console.log(fn5.length) // 0
length
function fn1(name, ...args) {}
console.log(fn1.length) // 1
var ary = [1, 2, 3, 4]
function fn(i){
return function(n){
console.log(n+ (i++))
}
}
var f = fn(10)
f(20) // 30 (n+10)
f(20) // 31 (n+11)
fn(20)(40) // 60
fn(30)(40) // 70
// console.log(i) // Uncaught ReferenceError: i is not defined
变量 item
使用 var
进行声明,由于变量提升,所以具有函数作用域。当 onfocus
的回调执行时,item.help
的值被决定。由于循环在事件触发之前早已执行完毕,变量对象 item
(被三个闭包所共享)已经指向了 helpText
的最后一项。
function setupHelp() {
...
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function () {
showHelp(item.help);
};
}
}
setupHelp();
function makeHelpCallback(help) {
return function () {
showHelp(help);
};
}
function setupHelp() {
...
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
}
}
setupHelp();
function setupHelp() {
...
for (var i = 0; i < helpText.length; i++) {
(function () {
var item = helpText[i];
document.getElementById(item.id).onfocus = function () {
showHelp(item.help);
};
})(); // 马上把当前循环项的 item 与事件回调相关联起来
}
}
setupHelp();
function setupHelp() {
...
for (let i = 0; i < helpText.length; i++) {
const item = helpText[i];
document.getElementById(item.id).onfocus = () => {
showHelp(item.help);
};
}
}
setupHelp();
function setupHelp() {
...
helpText.forEach(function (text) {
document.getElementById(text.id).onfocus = function () {
showHelp(text.help);
};
});
}
setupHelp();
var变量提升到全局,可以重复声明
for(var i=0;i<2;i++){
setTimeout(()=>{console.log(i)},1000)//3,3
}
for(var i=0;i<3;i++){
setTimeout(()=>{console.log(i)},1000)//3,3,3
}
console.log(i)//3
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, 1000);
})(i);
}
//或者
for(var i = 0;i < 5;i++)
{
setTimeout((function(i){
return () => console.log(i);
})(i),1000)
}
// 利用setTimeout的第三个参数,第三个参数将作为setTimeout第一个参数的参数
for (var i = 0; i < 5; i++) {
setTimeout(function fn(i) {
console.log(i);
}, 1000, i); // 第三个参数i,将作为fn的参数
}
var num = 10 // 60; 65
var obj = {
num: 20
}
//自执行obj.fn= function(n){this.num+=n...}
obj.fn = (function (num){
this.num = num * 3 // window.num=20*3
num++ // 21
return function(n){
this.num += n // 60 + 5 = 65;20 + 10 =30
num++ // 21 + 1 = 22;22 + 1 = 23 闭包引用num
console.log(num)
}
})(obj.num)
var fn = obj.fn
fn(5) // 22 this 指向 window
obj.fn(10) // 23 this 指向 obj
console.log(num, obj.num) // 65, 30
this.count=1
function func() {
console.log(++this.count)//2
}
func.count = 0
func()
obj = {
func() {
const arrowFunc = () => {
console.log(this._name)
}
return arrowFunc
},
_name: "obj",
}
obj.func()()//obj
func = obj.func
func()()//undefined
obj.func.bind({ _name: "newObj" })()()//newObj
obj.func.bind()()()//undefined
obj.func.bind({ _name: "bindObj" }).apply({ _name: "applyObj" })()//bindObj
//虽然按顺序创建作用域,不会报错a为声明
function foo() {
console.log(a);
}
function bar() {
var a="bar"
foo();
}
bar(); //undefined
var a="window"
var a = 1;
function foo(a, b) {
console.log(a); // 1
a = 2;
arguments[0] = 3;
var a;
console.log(a, this.a, b); // 3, 1, undefined
}
foo(a);
function test() {
var foo = 33;
if (foo) {// var foo
//foo+55是let 的 foo
let foo = foo + 55; // ReferenceError
}
}
test();
标识符 n.a 被解析为位于指令(let n)本身的 n 对象的属性 a。因为 n 的声明尚未执行结束,它仍然处于暂时性死区内
function go(n) {
// n 在此处被定义
console.log(n); // { a: [1, 2, 3] }
for (let n of n.a) {
// ^ ReferenceError
console.log(n);
}
}
go({ a: [1, 2, 3] });
= 优先级是从右到左的,所以变量提升阶段 b=undefined后,将 c 赋值成 undefined
var b = {
a,
c: b
}
console.log(b.c);
//undefined
//宏任务队列:[]
//微任务队列:[promise0]
Promise.resolve()
.then(function() {
console.log("promise0");
})
.then(function() {
console.log("promise5");
});
//定时的setTimeout(delay=0)=setImmediate:下个Event Loop执行
//宏任务队列:[timer1]
//微任务队列:[promise0]
setTimeout(() => {
console.log("timer1");
Promise.resolve().then(function() {
console.log("promise2");
});
Promise.resolve().then(function() {
console.log("promise4");
});
}, 0);
//宏任务队列:[timer1,timer2]
//微任务队列:[promise0]
setTimeout(() => {
console.log("timer2");
Promise.resolve().then(function() {
console.log("promise3");
});
}, 0);
//宏任务队列:[timer1,timer2]
//微任务队列:[promise0,promise1]
Promise.resolve().then(function() {
console.log("promise1");
});
//执行start
console.log("start");
//执行当前所有微任务队列:[promise0,promise1]
//执行promise0时将promise5放入了微任务队列:[promise1,promise5]
//接着执行微任务队列:输出promise1,promise5
//当微任务队列为空,开始执行宏任务队列[timer1,timer2]队首的timer1
//执行timer1时碰到了微任务promise2,放进微任务队列[promise2]
//宏任务timer1执行完了,开始执行所有当前所有微任务:[promise2]
//执行promise2完碰到微任务promise4,放进微任务队列:[promise4]
//当前微任务队列不为空,接着执行promise4
//微任务队列为空,接着执行宏任务队列队首[timer2]
//执行timer2时碰到了微任务promise3,放进微任务队列[promise3]
//宏任务timer2执行完了,开始执行所有当前所有微任务:[promise3]
// 打印结果: start promise0 promise1 promise5 timer1 promise2 promise4 timer2 promise3
//1.script start(同步)
console.log("script start");
async function async1() {
await async2(); // await 隐式返回promise
console.log("async1 end"); // 这里的执行时机:在执行微任务时执行
}
async function async2() {
console.log("async2 end"); // 这里是同步代码
}
//2.async2 end(同步)
//微任务队列:[async1 end]
async1();
//宏任务队列:[setTimeout],setTimeOut进入下一loop
setTimeout(function() {
console.log("setTimeout");
}, 0);
//3.Promise(同步)
//宏任务队列:[setTimeout]
//微任务队列:[async1 end,promise1]
new Promise(resolve => {
console.log("Promise"); // 这里是同步代码
resolve();
})
.then(function() {
console.log("promise1");
})
.then(function() {
console.log("promise2");
});
//4.script end(同步)
console.log("script end");
//当前loop的宏任务(都是同步代码)都执行完毕
//执行所有微任务[async1 end,promise1]
//执行promise1完后碰到了promise2,加入微任务队列,接着执行
//当前所有微任务都执行完毕,开始执行宏任务队列[setTimeout]
// 打印结果: script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout
console.log("start");
//定时进入下一loop,宏任务队列:[timeout]
setTimeout(() => {
console.log("timeout");
}, 0);
//微任务队列:[promise]
Promise.resolve().then(() => {
console.log("promise");
});
//process.nextTick在微任务队首
//微任务队列:[nextTick,promise]
process.nextTick(() => {
console.log("nextTick");
Promise.resolve().then(() => {
console.log("promise1");
});
});
console.log("end");
// 执行结果 start end nextTick promise promise1 timeout
在JavaScript中,字符串是不可变的(immutable),str是基本数据类型,或者string对象,String的方法都是返回新值
可变只有数组和对象,不可变可以带来性能和安全性的好处
let str=new String("123")
str[0]="z"
console.log(str[0])//1
let str = "Hello";
let index = str.indexOf("",0);
console.log(index); // 输出 0
index = str.indexOf("",3);
console.log(index); // 输出 3
index = str.indexOf("",6);
console.log(index); // 输出 5
编译器会从左到右一个字符一个字符解析,如果已解析的字符已经能够组成一个符号,再解析下一个字符,判断下一个字符能否和已解析出的符号再次组合成一个符号,如果能,再不断重复如上过程;如果不能,则使用最终解析出的符号。
var n=1
n=n++
console.log(n)//1
//等价于
var n=1;
//n=n++运算开始
var temp=n; //编译器解析到“n=n++”中的“n++”后,会先在内存中缓存n的原值,用来参与其他运算
n = n+1; //编译器解析到“n=n++”中的“=”后,会在做“=”运算前将n自加
n = temp; //变量自加结束后,用缓存的原值进行“=”运算
//n=n++运算结束
console.log(n) //1
var a=3,b;
b=a++*a++;
console.log(b) //12
var a=3,b;
b=a+++a; //b=(a++)+a, b=3+4
console.log(b) //7
参考链接:JS 经典面试题初篇(this, 闭包, 原型...)含答案 - 掘金