1、原型和原型链
当我们找实例对象的属性时,如果找不到,就会查找与对象关联的原型中去找,如果还找不到,就去找原型的原型,直到最顶层。
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);console.log(new B().a);console.log(new C(2).a);
复制代码
答案:1 undefined 2
解析:
//声明一个构造函数Afunction A() {}//声明一个构造函数B,传人参数a,自有属性a的值取决于传入的参数function B(a) { this.a = a;}//声明一个构造函数B,如果有参数,则添加自有属性a,属性a的值为传入的参数值,//如果没有传入参数,则构造函数C没有自有属性function C(a) { if (a) { this.a = a; }}A.prototype.a = 1;B.prototype.a = 1;C.prototype.a = 1;//构造函数function A(){},是没有自有属性的,然后就会顺着原型链查找,我们找到构造函数A的原型对象A.prototype上有属性a且等于1console.log(new A().a);//1console.log(new B().a);//undefinedconsole.log(new C(2).a);//2
复制代码
2、异步队列
微任务:Promise.then、async/await、MutaionObserver 宏任务:script(整体)、setTimeout、setInterval、Ajax、DOM 事件、postMessage 执行顺序如下:
async function async1() { console.log("a"); const res = await async2(); console.log("b"); } async function async2() { console.log("c"); return 2; }
console.log("d"); setTimeout(() => { console.log("e"); }, 0); async1().then(res => { console.log("f") }) new Promise((resolve) => { console.log("g"); resolve(); }).then(() => { console.log("h"); }); console.log("i");
复制代码
答案:d、a、c、g、i、b、h、f、e
解析:
//打印d//遇到setTimeout,放到宏任务队列//遇到async1().then(),先执行async1(),then函数注册到微任务队列(但不是立即注册,而是等async1()执行完之后才会注册)//在执行async1()时候 async1().then(res => { console.log("f") })//打印a//执行async1()时,当执行到 const res = await async2()时,await后先让后面的表达式先执行,也就是async2()//打印c//然后将其后面的代码放到微任务队列中,然后跳出async函数,执行后面的代码,console.log("c")被放到了微任务队列中//执行new Promise((resolve) => { console.log("g"); resolve(); }).then(),由于new Promise()是同步任务//打印g//new Promise后的then函数放到微任务队列//打印i//此时微任务队列[console.log("b"),console.log("h")][console.log("f")]//宏任务队列[console.log("e")]//先执行微任务,再执行宏任务//打印g、f、e
复制代码
setTimeout(function(){ console.log(1) },0)
new Promise(function(resolve,reject){ console.log(2) for(var i=0;i<10000;i++) { if(i===10){ console.log(10); } i==9999 && resolve() } console.log(3); }).then(function(){ console.log(4); }) console.log(5);
复制代码
答案:2 10 3 5 4 1
解析
//setTimeout放到宏任务队列//执行 new Promise()同步任务//打印2//打印10//打印3// new Promise()后的then函数 放到微任务队列//打印5微任务队列:[console.log(4)]宏任务队列:[ console.log(1)]
复制代码
Promise.reject(1). then(2). then(res => { return 3 }). catch(error => { return 4 }). then(Promise.resolve(5)). then(console.log)
复制代码
答案:4 解析:
//Promise.reject(1) 返回一个被拒绝的 Promise,其拒绝原因为数字 1。//.then(2) 返回一个新的 Promise,它的回调函数不会被调用,因为前一个 Promise 被拒绝了。//.then(res => { return 3 }) 返回一个新的 Promise,它的回调函数也不会被调用,因为前一个 Promise 被拒绝了。//.catch(error => { return 4 }) 返回一个新的 Promise,它的回调函数被调用并返回数字 4(作为一个被解决的 Promise)。//.then(Promise.resolve(5)) 返回一个新的 Promise,它的回调函数不会被调用,因为参数不是一个函数。//.then(console.log) 返回一个新的 Promise,它的回调函数被调用并打印数字 4。
复制代码
var F = function() {};Object.prototype.a = function() { console.log("a()");};Function.prototype.b = function() { console.log("b()");};var f = new F();F.a();F.b();f.a();f.b();
复制代码
答案:a b a f.b is not a function
解析:
//1、声明变量F,F既是对象也是函数,故F可调用原型上的方法,无论是对象还是函数上的//2、f = new F(),通过 构造函数 F new了一个实例f,此f只是一个对象并不是函数类型,故不能调用Function原型上的方法//证明:
复制代码
3、浅拷贝/深拷贝
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存深拷贝生成一个新对象,新对象跟原对象不共享内存,修改新对象不会改到原对象
var obj1 = { a: 1, b: 2, c: 3}var obj2 = obj1;obj2.a = 5;console.log(obj1.a); console.log(obj2.a);
复制代码
答案:1 5
解析
//var obj2 = obj1属于浅拷贝,故而obj1,obj2共用一个内存,打印出5,5
复制代码
var obj1 = {a: 1,b: 2,c: 3}var objString = JSON.stringify(obj1);var obj2 = JSON.parse(objString);obj2.a = 5;console.log(obj1.a);console.log(obj2.a);
复制代码
解析
//JSON.stringify() 方法将一个 JavaScript 对象或值转换为 JSON 字符串//JSON.parse() 方法用来解析 JSON 字符串,解析为 JavaScript 值或对象//JSON.parse(JSON.stringify())也常被用作深拷贝//深拷贝生成一个新对象,新对象跟原对象不共享内存,修改新对象不会改到原对象//打印1、5
复制代码
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
解析:
//在执行const wrapper = func.bind(obj)时,func的this指向obj//接着执行setTimeout,1s后执行wrapper//在上面的一秒内,重新赋值obj = {num:130},这并不会影响到已经绑定了 obj 的 wrapper 函数//所以打印出117
复制代码
4、作用域
JS 代码的执行是由浏览器中的 JS 解析器来执行的。JS 解析器执行 JS 代码的时候,分为两个过程:预解析过程,代码执行过程预解析过程:
1、把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。2、把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。3、先提升 var,在提升 function。
预解析顺序:
1、预解析的顺序是从上到下,函数的优先级高于变量,函数声明提前到当前作用域最顶端,在 var 之上。2、函数执行的时候,函数内部才会进行预解析,如果有参数,先给实参赋值再进行函数内部的预解析。3、预解析函数是声明+定义(开辟了内存空间,形参值默认是 undefined)。4、预解析变量是只声明,不赋值,默认为 undefined。5、==<font color=red >函数重名时,后者会覆盖前者。 </font>==6、==<font color=red >变量重名时,不会重新声明,但是执行代码的时候会重新赋值。</font>==7、变量和函数重名的时候,函数的优先级高于变量
函数执行顺序:
1、形成私有作用域 2、形参赋值(也是私有变量)3、变量提升 4、代码从上到下执行 5、作用域是否销毁
函数作用域不销毁的条件:
1、函数的返回值为引用类型 2、返回值被其他变量使用
var a = 0, b = 0function A(a) { A = function(b) { console.log(a + b++) } console.log(a++)}A(1)A(2)
复制代码
答案:2 4
解析
//执行A(1),传入参数1//修改函数A的值为:function(b) { console.log(a + b++) }// 执行 console.log(a++),打印出1
//执行A(2),传入参数2//由于A函数被修改,所以执行的的是function(b) { console.log(a + b++) }// 此时a为2,传入的b为2 ,所以打印4
复制代码
console.log(a); var a=12;function fn(){ console.log(a); a=13; } fn();console.log(a);
复制代码
答案:undefined 12 13
解析:
//如果在函数中定义变量时,如果不添加var关键字, 这个变量是一个全局变量 //打印undefined//由于a=13在定义a变量没有用关键字,所以在这里是全局变量//fn执行console.log(a)时没有找到私有变量a,会沿着作用域链查找变量//打印12//紧接着a=13修改全局变量//打印13
复制代码
console.log(a); a=2;function fn(){ console.log(a); a=3; } fn();console.log(a);
复制代码
答案:Uncaught ReferenceError: a is not defined
解析:
//变量a不会被提升,因为没有var声明,//如果在函数中定义变量时,如果不添加var关键字, 这个变量是一个全局变量 //所以会报错:Uncaught ReferenceError: a is not defined
复制代码
var a=10,b=11,c=12; function test(a){ a=1;var b=2;c=3; } test(10);alert(a); alert(b); alert(c);
复制代码
答案:1 11 3
var foo = function () { console.log("foo1")}foo()
var foo = function () { console.log("foo2")}foo()
function foo() { console.log("foo1")}foo()
function foo() { console.log("foo2")}foo()
复制代码
答案:foo1 foo2 foo2 foo2
解析:
//该题可以把代码分开var foo = function () { console.log("foo1")}foo()
var foo = function () { console.log("foo2")}foo()
//1、声明了变量foo,因为声明变量重名了,故不会重新声明,//2、执行代码时候,会先执行function () { console.log("foo1")}//3、又对变量进行重新复制,故执行function () { console.log("foo2")}//所以打印出 foo1 foo2----------------------------------------function foo() { console.log("foo1")}foo()
function foo() { console.log("foo2")}foo()//1、在预解析的时候,由于声明函数foo重名,故后者会覆盖前者,所以最后foo函数声明的结果如下function foo() { console.log("foo2")}//2、执行打印出foo2 foo2
复制代码
5、矩阵转置
将 [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]——>[ [1, 4, 7], [2, 5, 8], [3, 6, 9] ]
let a = [ [1,2,3], [4,5,6], [7,8,9]]let b = [ [1,4,7], [2,5,8], [3,6,9]]//其实不难发现 a[0][0] = b[0][0],a[0][1] = b[1][0],a[0][2] = b[2][0]//也就是 a[i][j] = b[j][i]
复制代码
解析:
function transposeArray(array) { // 获取原行数和列数 const rows = array.length; const cols = array[0].length; // 创建一个新的二维数组,长度等于原来数组列数 const transposedArray = []; for (let j = 0; j < cols; j++) { transposedArray[j] = new Array(rows); } // 将元素从原数组复制到新数组中 for (let i = 0; i < rows; i++) { for (let j = 0; j < cols; j++) { transposedArray[j][i] = array[i][j]; } } return transposedArray;}const myArray = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];const transposedArray = transposeArray(myArray);console.log(transposedArray);// 输出:[[1, 4, 7], [2, 5, 8], [3, 6, 9]]
复制代码
6、类、继承
class cls{ constructor(){ this.show() } show(){ console.log("yoo") } } class son extends cls{ constructor(){ super() } show(){ console.log('ohh') } } new son() new cls()
复制代码
答案:ohh yoo
解析
//1、在创建 son 对象时,会先调用父类 cls 的构造函数//2、然后再调用子类 son 的构造函数。//3、在子类 son 的构造函数中,没有显式调用父类的 show 方法,因此会直接执行子类中重写的 show 方法,输出 “ohh”。//4、接着创建 cls 对象时,同样会调用父类 cls 的构造函数,执行父类的 show 方法,输出 “yoo”
复制代码
评论