Appearance
代码输出
代码输出 - 1
js
function side(arr) {
arr[0] = arr[2];
}
function a(a, b, c = 3) {
c = 10;
side(arguments);
return a + b + c;
}
a(1, 1, 1);
答案
txt
12
function a(a, b, c = 3) 这里的 c,因为 a 函数加了默认值,所以就按 ES 的方式解析,函数中的参数就不会变了
js
function side(arr) {
arr[0] = arr[2];
}
function a(a, b, c = 3) {
c = 10;
console.log(arguments);
side(arguments); // 这里 a,c的值不管怎么改变都是不会改变的
return a + b + c;
}
a(1, 1, 1); //12
但是,如果是
js
function side(arr) {
arr[0] = arr[2];
}
function a(a, b, c) {
c = 10;
console.log(arguments);
side(arguments); // 这里 a,c的值不管怎么改变都是不会改变的
return a + b + c;
}
a(1, 1, 1); // 21
代码输出 - 2
js
var min = Math.min();
max = Math.max();
console.log(min < max);
答案
txt
false
Math.min 的参数是 0 个或者多个,如果多个参数很容易理解,返回参数中最小的。如果没有参数,则返回 Infinity,无穷大。
而 Math.max 没有传递参数时返回的是-Infinity.所以输出 false
代码输出 - 3
js
var a = 1;
(function a() {
a = 2;
console.log(a);
})();
答案
txt
ƒ a () {
a = 2;
console.log(a);
}
立即调用的函数表达式(IIFE) 有一个 自己独立的 作用域,如果函数名称与内部变量名称冲突,就会永远执行函数本身;所以上面的结果输出是函数本身;
立即执行的函数表达式(IIFE)的函数名称跟内部变量名称重名后,函数名称优先,因为函数名称是不可改变的,内部会静默失败,在严格模式下会报错
代码输出 - 4
js
(function () {
var a = (b = 5);
})();
console.log(b);
console.log(a);
// 写出执行结果,并解释原因
答案
txt
5
Error, a is not defined
在这个立即执行函数表达式(IIFE)中包括两个赋值操作,其中 a 使用 var 关键字进行声明,因此其属于函数内部的局部变量(仅存在于函数中),相反,b 被分配到全局命名空间。
另一个需要注意的是,这里没有在函数内部使用严格模式(use strict;)。如果启用了严格模式,代码会在输出 b 时报错 Uncaught ReferenceError: b is not defined,需要记住的是,严格模式要求你显式的引用全局作用域。
js
(function () {
"use strict";
var a = (b = 5);
})();
console.log(b); //Uncaught ReferenceError: b is not defined
/*---------------------------*/
(function () {
"use strict";
var a = (window.b = 5);
})();
console.log(b); // 5
代码输出 - 5
js
var company = {
address: "beijing",
};
var yideng = Object.create(company);
delete yideng.address;
console.log(yideng.address);
// 写出执行结果,并解释原因
答案
txt
beijing
解析 这里的 yideng 通过 prototype 继承了 company 的 address。yideng 自己并没有 address 属性。所以 delete 操作符的作用是无效的。
知识点 1.delete 使用原则:delete 操作符用来删除一个对象的属性。 2.delete 在删除一个不可配置的属性时在严格模式和非严格模式下的区别: (1)在严格模式中,如果属性是一个不可配置(non-configurable)属性,删除时会抛出异常; (2)非严格模式下返回 false。 3.delete 能删除隐式声明的全局变量:这个全局变量其实是 global 对象(window)的属性 4.delete 能删除的: (1)可配置对象的属性(2)隐式声明的全局变量 (3)用户定义的属性 (4)在 ECMAScript 6 中,通过 const 或 let 声明指定的 "temporal dead zone" (TDZ) 对 delete 操作符也会起作用 delete 不能删除的: (1)显式声明的全局变量 (2)内置对象的内置属性 (3)一个对象从原型继承而来的属性 5.delete 删除数组元素: (1)当你删除一个数组元素时,数组的 length 属性并不会变小,数组元素变成 undefined (2)当用 delete 操作符删除一个数组元素时,被删除的元素已经完全不属于该数组。 (3)如果你想让一个数组元素的值变为 undefined 而不是删除它,可以使用 undefined 给其赋值而不是使用 delete 操作符。此时数组元素是在数组中的 6.delete 操作符与直接释放内存(只能通过解除引用来间接释放)没有关系。
7.其它例子 (1)下面代码输出什么?
js
var output = (function (x) {
delete x;
return x;
})(0);
console.log(output);
答案:0,delete 操作符是将 object 的属性删去的操作。但是这里的 x 是并不是对象的属性, delete 操作符并不能作用。
(2)下面代码输出什么?
js
var x = 1;
var output = (function () {
delete x;
return x;
})();
console.log(output);
答案:输出是 1。delete 操作符是将 object 的属性删去的操作。但是这里的 x 是并不是对象的属性, delete 操作符并不能作用。
(3)下面代码输出什么?
js
x = 1;
var output = (function () {
delete x;
return x;
})();
console.log(output);
答案:报错 VM548:1 Uncaught ReferenceError: x is not defined,
(4)下面代码输出什么?
js
var x = { foo: 1 };
var output = (function () {
delete x.foo;
return x.foo;
})();
console.log(output);
答案:输出是 undefined。x 虽然是全局变量,但是它是一个 object。delete 作用在 x.foo 上,成功的将 x.foo 删去。所以返回 undefined
代码输出 - 6
js
var foo = function bar() {
return 12;
};
console.log(typeof bar());
// 写出执行结果,并解释原因
答案
txt
输出是抛出异常,bar is not defined。
解析 这种命名函数表达式函数只能在函数体内有效
js
typeof bar; // "undefined"
typeof foo(); // "number"
typeof foo; // "function"
typeof bar(); // Uncaught ReferenceError: bar is not defined
代码输出 - 7
js
var x = 1;
if (function f() {}) {
x += typeof f;
}
console.log(x);
// 写出执行结果,并解释原因
答案
txt
1undefined
解析 条件判断为假的情况有:0,false,'',null,undefined,未定义对象。函数声明写在运算符中,其为 true,** 但放在运算符中的函数声明在执行阶段是找不到的。**另外,对未声明的变量执行 typeOf 不会报错,会返回 undefined
代码输出 - 8
js
["1", "2", "3"].map(parseInt);
答案
txt
[1,NaN,NaN]
解析
1)Array.prototype.map()
array.map(callback[, thisArg])
callback 函数的执行规则
参数:自动传入三个参数
currentValue(当前被传递的元素);
index(当前被传递的元素的索引);
array(调用 map 方法的数组)
2)parseInt 方法接收两个参数
第三个参数["1", "2", "3"]将被忽略。parseInt 方法将会通过以下方式被调用
parseInt("1", 0)
parseInt("2", 1)
parseInt("3", 2)
3)parseInt 的第二个参数 radix 为 0 时,ECMAScript5 将 string 作为十进制数字的字符串解析;
parseInt 的第二个参数 radix 为 1 时,解析结果为 NaN;
parseInt 的第二个参数 radix 在 2—36 之间时,如果 string 参数的第一个字符(除空白以外),不属于 radix 指定进制下的字符,解析结果为 NaN。
parseInt("3", 2)执行时,由于"3"不属于二进制字符,解析结果为 NaN。
代码输出 - 9
js
[typeof null, null instanceof Object];
答案
txt
[object, false]
代码输出 - 10
js
function f() {}
const a = f.prototype,
b = Object.getPrototypeOf(f);
console.log(a === b);
答案
txt
false
f.prototype 是使用使用 new 创建的 f 实例的原型. 而 Object.getPrototypeOf 是 f 函数的原型.
a === Object.getPrototypeOf(new f()) // true
b === Function.prototype // true
代码输出 - 11
js
function showCase(value) {
switch (value) {
case "A":
console.log("Case A");
break;
case "B":
console.log("Case B");
break;
case undefined:
console.log("undefined");
break;
default:
console.log("Do not know!");
}
}
showCase(new String("A"));
答案
txt
Do not know!
switch 是严格比较, String 实例和 字符串不一样.
js
var str1 = "str";
var str2 = new String("str");
console.log(typeof str1); // "string"
console.log(typeof str2); //"object"
代码输出 - 12
js
console.log([2, 1, 0].reduce(Math.pow));
console.log([].reduce(Math.pow));
console.log([1, 0].reduce(Math.pow));
console.log([3].reduce(Math.pow));
console.log([].reduce(Math.pow, 3));
答案
txt
1
在没有初始值的空数组上调用 reduce 将报错。
1
3
3
代码输出 - 13
js
var arr = [0, 1];
arr[5] = 5;
newArr = arr.filter(function (x) {
return x === undefined;
});
console.log(newArr.length);
答案
txt
0
filter 为数组中的每个元素调用一次 callback 函数,并利用所有使得 callback 返回 true 或等价于 true 的值的元素创建一个新数组。callback 只会在已经赋值的索引上被调用,对于那些已经被删除或者从未被赋值的索引不会被调用。那些没有通过 callback 测试的元素会被跳过,不会被包含在新数组中。
代码输出 - 14
js
// a 在什么情况下会打印 1
var a; // ?;
if (a == 1 && a == 2 && a == 3) {
console.log(1);
}
答案
txt
// 方法1 -------------
var a = {
i: 1,
toString: function () {
return a.i++;
}
// or
valueOf() {
return this.i++;
}
}
// 方法2 -------------
var a = [1,2,3];
a.join = a.shift;
代码输出 - 15
js
const obj = {
2: 3,
3: 4,
length: 2,
splice: Array.prototype.splice,
push: Array.prototype.push,
};
obj.push(1);
obj.push(2);
console.log(obj);
答案
txt
Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ]
代码输出 - 16
js
const num = parseInt("2*4", 10);
console.log(num);
答案
txt
2
只返回了字符串中第一个字母. 设定了 进制 后 (也就是第二个参数,指定需要解析的数字是什么进制: 十进制、十六机制、八进制、二进制等等), parseInt 检查字符串中的字符是否合法. 一旦遇到一个在指定进制中不合法的字符后,立即停止解析并且忽略后面所有的字符。 *就是不合法的数字字符。所以只解析到 2,并将其解析为十进制的 2. num 的值即为 2
代码输出 - 17
js
const company = { name: "tom" };
Object.defineProperty(company, "address", { value: "北京" });
console.log(company);
console.log(Object.keys(company));
答案
txt
{name: 'tom', address: '北京'}
['name']
通过 defineProperty 方法,我们可以给对象添加一个新属性,或者修改已经存在的属性。而我们使用 defineProperty 方法给对象添加了一个属性之后,属性默认为 不可枚举(not enumerable).
Object.keys 方法仅返回对象中 可枚举(enumerable) 的属性,因此只剩下了 name
用 defineProperty 方法添加的属性默认不可变。你可以通过 writable, configurable 和 enumerable 属性来改变这一行为。这样的话, 相比于自己添加的属性, defineProperty 方法添加的属性有了更多的控制权。
代码输出 - 18
js
let num = 10;
const increaseNumber = () => num++;
const increasePassedNumber = (number) => number++;
const num1 = increaseNumber();
const num2 = increasePassedNumber(num1);
console.log(num1);
console.log(num2);
console.log(num);
答案
txt
10
10
11
a++ 与 ++a 的区别
代码输出 - 19
js
const value = { number: 10 };
const multiply = (x = { ...value }) => {
console.log((x.number *= 2));
};
multiply();
multiply();
multiply(value);
multiply(value);
答案
txt
20
20
20
40
/*
在ES6中,我们可以使用默认值初始化参数。如果没有给函数传参,或者传的参值为 "undefined" ,那么参数的值将是默认值。上述例子中,我们将 value 对象进行了解构并传到一个新对象中,因此 x 的默认值为 {number:10} 。
默认参数在调用时才会进行计算,每次调用函数时,都会创建一个新的对象。我们前两次调用 multiply 函数且不传递值,那么每一次 x 的默认值都为 {number:10} ,因此打印出该数字的乘积值为 20。
第三次调用 multiply 时,我们传递了一个参数,即对象 value。*=运算符实际上是 x.number=x.number*2的简写,我们修改了 x.number的值,并打印出值 20。
第四次,我们再次传递 value对象。x.number之前被修改为 20,所以 x.number*=2打印为 40。
*/
代码输出 - 20
js
[1, 2, 3, 4].reduce((x, y) => console.log(x, y));
答案
txt
1 2
undefined 3
undefined 4
代码输出 - 21
js
// index.js
console.log("running index.js");
import { sum } from "./sum.js";
console.log(sum(1, 2));
// sum.js
console.log("running sum.js");
export const sum = (a, b) => a + b;
答案
txt
running sum.js
running index.js
3
import 命令是编译阶段执行的,在代码运行之前。因此这意味着被导入的模块会先运行,而导入模块的文件会后执行。 这是 CommonJS 中 require()和 import 之间的区别。使用 require(),可以在运行代码时根据需要加载依赖项。
如果我们使用 require 而不是 import,则 running index.js、running sum.js、 3 会被依次打印。
代码输出 - 22
js
function addToList(item, list) {
return list.push(item);
}
const result = addToList("company", ["yideng"]);
console.log(result);
答案
txt
2
push()方法返回新数组的长度。一开始,数组包含一个元素(字符串 "yideng"),长度为 1。 在数组中添加字符串 "company"后,长度变为 2,并将从 addToList 函数返回。
push 方法修改原始数组,如果你想从函数返回数组而不是数组长度,那么应该在 push item 之后返回 list。 开发中一不小心会导致错误的地方
写出解决方式
js
var obj = { x: 1, y: 2, z: 3 };
[...obj]; // TypeError
// 能否以某种方式为上面的语句使用展开运算而不导致类型错误
// 如果可以,写出解决方式
答案
展开语法和 for-of 语句遍历 iterabl 对象定义要遍历的数据。Arrary 和 Map 是具有默认迭代行为的内置迭代器。对象不是可迭代的,但是可以通过使用 iterable 和 iterator 协议使它们可迭代。
在 Mozilla 文档中,如果一个对象实现了@iterator 方法,那么它就是可迭代的,这意味着这个对象(或者它原型链上的一个对象) 必须有一个带有@iterator 键的属性,这个键可以通过常量 Symbol.iterator 获得。
js
obj[Symbol.iterator] = function () {
const _this = this;
//也可使用: keys = Object.getOwnPropertyNames(this)
const keys = Object.keys(this);
let index = 0;
return {
next() {
return {
value: _this[keys[index++]],
done: index > keys.length,
};
},
};
};
obj[Symbol.iterator] = function* () {
const values = Object.values(this);
for (const value of values) {
yield value;
}
};
代码输出 - 23
js
function foo() {
console.log(length);
}
function bar() {
var length = "京程一灯";
foo();
}
bar();
答案
txt
0
输出结果是 0,因为 foo 函数是由 window 对象调用,打印的 length 是 window 对象下的 length 属性 0。foo 只是在 bar 函数内部调用,并不是在 bar 函数内部声明,所以无法获取到 bar 函数声明的 length 变量
代码输出 - 24
js
let ydObject = { ...null, ...undefined };
console.log(ydObject);
let ydArray = [...null, ...undefined];
console.log(ydArray);
答案
txt
第一个打印一个空对象
第二个打印报错,是因为在数组和函数中使用展开语法时,只能用于可迭代对象
代码输出 - 25
js
const arrLike = {
length: 4,
0: 0,
1: 1,
"-1": 2,
3: 3,
4: 4,
};
console.log(Array.from(arrLike));
console.log(Array.prototype.slice.call(arrLike));
答案
txt
[0, 1, undefined, 3]
[0, 1, 空白, 3]
代码输出 - 26
js
var fullname = "a";
var obj = {
fullname: "b",
prop: {
fullname: "c",
getFullname: function () {
return this.fullname;
},
},
};
console.log(obj.prop.getFullname());
var test = obj.prop.getFullname;
console.log(test());
答案
txt
c
a
原因在于 this 指向的是函数的执行环境,this 取决于其被谁调用了,而不是被谁定义了。
对第一个 console.log()语句而言,getFullName()是作为 obj.prop 对象的一个方法被调用的,因此此时的执行环境应该是这个对象。
另一方面,但 getFullName()被分配给 test 变量时,此时的执行环境变成全局对象(window),原因是 test 是在全局作用域中定义的。因此,此时的 this 指向的是全局作用域的 fullname 变量,即 a。
代码输出 - 27
js
var foo = {
bar: function () {
return this.baz;
},
baz: 1,
};
console.log(typeof (f = foo.bar)());
答案
txt
"undefined"
这里并不是因为赋值给 f ,相当于 f(),所以 this 指向 window 的。 不然试试下面代码也会打印 undefined
js
var foo = {
bar: function () {
return this.baz;
},
baz: 1,
};
console.log(typeof (foo.bar = foo.bar)());
下面简单从规范的角度判断这个 this 指向,可以分以下几个步骤:
1.计算 MemberExpression 的结果赋值给 ref
2.判断 ref 是不是一个 Reference 类型
2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)
2.2 如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么 this 的值为 ImplicitThisValue(ref)
2.3 如果 ref 不是 Reference,那么 this 的值为 undefined
解释下这两个步骤: MemberExpression 我们可以简单理解为括号前的部分,针对这题就是 (f=foo.bar) 这部分。
Reference 是规范类型的一种。如果通过 GetValue 方法从 Reference 类型中获取值,则该 MemberExpression 返回结果不再是 Reference 类型。 这里的关键就是判断 MemberExpression 的返回结果是不是 Reference 类型。 由于 f=foo.bar 存在赋值操作符,根据规范 11.13.1 Simple Assignment ( = ) 规定,其第三步使用了 GetValue(ref),故返回值不是 Reference 类型。 对照上述 2.3 的规范,如果表达式返回值不是 Reference 类型,那么 this 的值为 undefined,在非严格模式下,被隐式转换为全局对象 window。
代码输出 - 28
js
const num = {
a: 10,
add() {
return this.a + 2;
},
reduce: () => this.a - 2,
};
console.log(num.add());
console.log(num.reduce());
答案
txt
12
NaN
注意,add 是普通函数,而 reduce 是箭头函数。对于箭头函数,this 关键字指向是它所在上下文(定义时的位置)的环境,与普通函数不同! 这意味着当我们调用 reduce 时,它不是指向 num 对象,而是指其定义时的环境(window)。没有值 a 属性,返回 undefined。
代码输出 - 29
js
const person = { name: "yideng" };
function sayHi(age) {
return `${this.name} is ${age}`;
}
console.log(sayHi.call(person, 21));
console.log(sayHi.bind(person, 21));
答案
txt
yideng is 21
ƒ sayHi(age) {return ${this.name} is ${age};}
使用两者,我们可以传递我们想要 this 关键字引用的对象。
.call 方法会立即执行!
.bind 方法会返回函数的拷贝值,但带有绑定的上下文!它不会立即执行。
代码输出 - 30
js
let length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function (fn) {
fn();
arguments[0]();
},
};
obj.method(fn, 1);
答案
txt
0
2
1)fn()知识点 ①fn()知识点,任意函数里如果嵌套了 非箭头函数,那这个时候 嵌套函数里的 this 在未指定的情况下,应该指向的是 window 对象,所以这里执行 fn 会打印 window.length,但是 let 声明的变量会形成块级作用域,且不存在声明提升,而 var 存在声明提升。所以当使用 let 声明变量时,不存在声明提升,length 属性实际上并没有添加到 window 对象中。 // 例如在浏览器控制台 let a = 1; window.a // undefined var b = 1; window.b // 1 但是这里为什么输出 0 呢,因为 window 对象原先上有 length 属性,所以输出的是原先的值 0
②arguments 知识点 在方法调用(如果某个对象的属性是函数,这个属性就叫方法,调用这个属性,就叫方法调用)中,执行函数体的时候,作为属性访问主体的对象和数组便是其调用方法内 this 的指向。(通俗的说,调用谁的方法 this 就指向谁; arguments[0]指向 fn,所以 arguments[0]() 是作为 arguments 对象的属性[0]来调用 fn 的,所以 fn 中的 this 指向属性访问主体的对象 arguments;这里 this 指向 arguments。 因为 fn 作为一个参数存储在 arg 对象里,argumengts 的长度为 2,所以输出 2 // 例如 [function fn(){console.log(this.length)}][0]; // 1 // 数组也是对象,只不过数组对象的整型属性会计入 length 属性中,并被区别对待,这里就是调用数组对象的 0 属性,函数作为数组对象的属性调用,函数中的 this 当然指向这个数组,所以返回数组的 length
代码输出 - 31
txt
var a = 10;
var foo = {
a: 20,
bar: function () {
var a = 30;
return this.a;
},
};
console.log(foo.bar());
console.log((foo.bar)());
console.log((foo.bar = foo.bar)());
console.log((foo.bar, foo.bar)());
答案
txt
20
20
10
10
代码输出 - 32
js
function f() {
return f;
}
console.log(new f() instanceof f);
答案
txt
false
解析 a instanceof b 用于检测 a 是不是 b 的实例。如果题目 f 中没有 return f,则答案明显为 true;而在本题中 new f()其返回的结果为 f 的函数对象,其并不是 f 的一个实例。
js
function f() {}
console.log(new f() instanceof f);
// 答案:true
代码输出 - 33
js
function Foo() {
Foo.a = function () {
console.log(1);
};
this.a = function () {
console.log(2);
};
}
Foo.prototype.a = function () {
console.log(3);
};
Foo.a = function () {
console.log(4);
};
Foo.a();
let obj = new Foo();
obj.a();
Foo.a();
答案
txt
4
2
1
首先,
js
function Foo() {
Foo.a = function () {
console.log(1);
};
this.a = function () {
console.log(2);
};
}
上面这段代码执行完之后,我们有了一个名为 Foo 的函数,虽然函数里面也有一些代码,但此时它们还不会被执行,接下来
Foo.prototype.a = function() {console.log(3)}
到这里,我们知道了这个函数所有的实例都会有一个叫做 a 的方法,执行这个方法之后会输出 3,再往下看
Foo.a = function() {console.log(4)}
我们又给 Foo 函数添加了一个静态方法,也叫做 a,执行这个方法时会输出 4,继续看接下来的代码
Foo.a();
毫无疑问,调用了 Foo 函数的静态方法 a,刚才也说了会输出 4,所以这里会输出一个 4
let obj = new Foo();
然后我们使用 new 操作符以 Foo 构造函数创建了一个实例叫做 obj,创建实例时会执行构造函数的代码,也就是 Foo 里面的代码被执行了。首先,
Foo.a = function() {console.log(1)}
Foo 函数的静态方法 a 被修改了,原来是输出 4,修改之后会输出 1。接着
this.a = function() {console.log(2)}
使用 new 操作符创建实例时,构造函数中的 this 会指向新创建的实例,在这里新创建的实例就是 obj,因此 obj 有了一个方法叫做 a,执行这个方法会输出 2。同时,在 obj 的原型链上存在一个同样叫做 a 的方法,那个方法执行时会输出 3,也就是上面通过
Foo.prototype.a = function() {console.log(3)}
声明的方法,显而易见,对于 obj 来说,自身的方法 a 会覆盖原型链上的同名方法,因此,在下面的代码中调用 obj.a();时会输出 2。 最后,再次调用 Foo.a();时,由于刚在在创建 obj 实例时,Foo 函数的静态方法 a 已经被修改了,因此这里执行的是修改后的函数,所以会输出 1。 综上所述,最终输出结果为
代码输出 - 34
js
function user(obj) {
obj.name = "aaa";
obj = new Object();
obj.name = "bbb";
}
let person = new Object();
user(person);
console.log(person.name);
答案
txt
aaa
js
function user(obj) {
// obj传入的是引用
obj.name = "aaa"; // 修改引用的值
obj = new Object(); // obj 指向了一个新地址
obj.name = "bbb"; // 修改的是新对象,没法改变传入的对象
}
代码输出 - 35
js
function fn() {
getValue = function () {
console.log(1);
};
return this;
}
fn.getValue = function () {
console.log(2);
};
fn.prototype.getValue = function () {
console.log(3);
};
var getValue = function () {
console.log(4);
};
function getValue() {
console.log(5);
}
//请写出以下输出结果:
getValue();
fn().getValue();
getValue();
new fn.getValue();
new fn().getValue();
答案
txt
4
1
1
2
3
- 变量提升
js
var getValue = function () {
console.log(4);
};
function getValue() {
console.log(5);
}
// 相当于下面代码,所以第一个输出 4
function getValue() {
console.log(5);
}
var getValue;
getValue = function () {
console.log(4);
};
// ======
function fn() {
getValue = function () {
console.log(1);
};
return this;
}
fn().getValue();
//执行fn()函数,将getValue重新赋值,返回this,this 指向 window,
// 执行window.getValue() 如下代码,输出 1
getValue = function () {
console.log(1);
};
// ====
getValue(); // 输出 1
// ====
fn.getValue = function () {
console.log(2);
};
new fn.getValue(); // 输出 2
// ====
fn.prototype.getValue = function () {
console.log(3);
};
new fn().getValue(); //创建一个实例,调用getValue方法,输出 3
代码输出 - 36
js
function test() {}
const a = {},
b = Object.prototype;
console.log(a.prototype === b);
console.log(Object.getPrototypeOf(a) === b);
console.log(test.prototype === Object.getPrototypeOf(test));
答案
txt
false
true
false
prototype 属性是只有函数才特有的属性,当你创建一个函数时,js 会自动为这个函数加上 prototype 属性,值是一个空对象。而实例对象是没有 prototype 属性的。所以 a.prototype 是 undefined
Object 实际上是一个构造函数(typeof Object 的结果为"function"),使用字面量创建对象和 new Object 创建对象是一样的,所以 a.__proto__也就是 Object.prototype,所以 Object.getPrototypeOf(a)与 a.__proto__是一样的,第二个结果为 true
f.prototype 是使用使用 new 创建的 f 实例的原型: f.prototype === Object.getPrototypeOf(new f()); // true
Object.getPrototypeOf(f)是 f 函数的原型: Object.getPrototypeOf(f) === Function.prototype; //true
代码输出 - 37
js
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();
答案
txt
a
b
a
f.b is not a function
F instanceof Object == true F instanceof Function == true
f instanceof F // true f instanceof Function // false f instanceof Object // true
代码输出 - 38
js
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
答案
txt
0 1 2
3 3 3
代码输出 - 39
js
let a = { n: 1 };
let b = a;
a.x = a = { n: 2 };
console.log(a.x);
console.log(b.x);
答案
txt
undefined
{n:2}
点号的运算优先级大于等于号 赋值操作从右向左 所以, a.x=a={n:2} 可以表示为 a.x=(a={n:2}). a.x 的值是先计算的,此时 a 的指向依然是对象{n:1}. a.x 的值是括号里的返回值, 所以是在对象{n:1}上添加了 x 这个属性,属性值为{n:2}. 然后是变量 a 被重新赋值,指向了对象{n:2}. 但变量 b 的指向依然是原先的对象. 所以, a.x 的值是 undefined, b.x 的值是{n:2}.
代码输出 - 40
js
var a1 = {},
b1 = "123",
c1 = 123;
a1[b1] = "b";
a1[c1] = "c";
console.log(a1[b1]); // c
var a2 = {},
b2 = Symbol("123"),
c2 = Symbol("123");
a2[b2] = "b";
a2[c2] = "c";
console.log(a2[b2]); // b
var a3 = {},
b3 = { key: "123" },
c3 = { key: "456" };
a3[b3] = "b";
a3[c3] = "c";
console.log(a3[b3]); // c
答案
txt
c
b
c
数字做对象的 key 会转成字符串, 所以 a1 = { '123': 'c' } a2 = { Symbol(123): 'b', Symbol(123): 'c' }, Symbol 是唯一的, 所以 a2[b2] = b a3 = { [object Object]: 'c' } obj 做 key 会调用 toString 方法
代码输出 - 41
js
let x, y;
try {
throw new Error();
} catch (x) {
x = 1;
y = 2;
console.log(x);
}
console.log(x);
console.log(y);
答案
txt
1
undefined
2
需要注意的是 catch 的作用域,其实并不是常见的块作用域,并不能绑定自己的内部声明的变量。catch 创建的块作用域,只对 catch 的参数有效。对于在内部声明的变量,catch 并没有创建一个新的作用域,只是一个普通的代码块。
代码输出 - 42
✨
js
var a = 0;
if (true) {
a = 10;
console.log(a, window.a);
function a() {}
console.log(a, window.a);
a = 20;
console.log(a, window.a);
}
console.log(a);
答案
txt
10 0
10 10
20 10
10
代码输出 - 43
js
const lowerCaseOnly = /^[a-z]+$/;
console.log(lowerCaseOnly.test("yideng"));
console.log(lowerCaseOnly.test(null));
console.log(lowerCaseOnly.test());
答案
txt
true
true
true
test 方法的参数会被调用 toString 强制转换成字符串
代码输出 - 44
js
Object.prototype.name = "tom";
var a = 123;
a.b = 456;
console.log(a.name);
console.log(a.b);
答案
txt
tom
undefined
var a // 声明提升
Object.prototype.name = 'tom'; // 向对象添加属性,此时 a 对象身上有此属性
a = 123; //赋值,a 为数字类型
a.b = 456; // 数字类型无法这样赋值,所以打印 undefined
代码输出 - 45
js
const promise = new Promise((resolve, reject) => {
console.log(1);
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
答案
txt
1
2
4
promise.then 是微任务,它会在所有的宏任务执行完之后才会执行,同时需要 promise 内部的状态发生变化,因为这里内部没有发生变化,一直处于 pending 状态,所以不输出 3。
代码输出 - 46
js
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);
答案
txt
promise1
1 Promise {<fulfilled>: '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,执行它。
代码输出 - 47
js
console.log(1);
setTimeout(() => {
console.log(2);
process.nextTick(() => {
console.log(3);
});
new Promise((resolve) => {
console.log(4);
resolve();
}).then(() => {
console.log(5);
});
});
new Promise((resolve) => {
console.log(7);
resolve();
}).then(() => {
console.log(8);
});
process.nextTick(() => {
console.log(6);
});
setTimeout(() => {
console.log(9);
process.nextTick(() => {
console.log(10);
});
new Promise((resolve) => {
console.log(11);
resolve();
}).then(() => {
console.log(12);
});
});
答案
node>=11
txt
1
7
6
8
2
4
3
5
9
11
10
12
代码输出 - 48
js
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function () {
console.log("setTimeout");
}, 0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log("script end");
答案
txt
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
代码输出 - 49
js
function getName() {
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, i * 1000);
}
return;
{
name: "京程一灯";
}
}
console.log(getName());
答案
txt
undefined
0
1
2
3
4
代码输出 - 50
js
function yideng(n, o) {
console.log(o); // ?
return {
yideng: function (m) {
return yideng(m, n);
},
};
}
const a = yideng(0);
a.yideng(1);
a.yideng(2);
a.yideng(3);
const b = yideng(0).yideng(1).yideng(2).yideng(3);
const c = yideng(0).yideng(1);
c.yideng(2);
c.yideng(3);
答案
txt
undefined
0
0
0
undefined
0
1
2
undefined
0
1
1
闭包
代码输出 - 51
js
var a = [0];
if (a) {
console.log(a == true);
} else {
console.log(a);
}
答案
txt
false
1)当 a 出现在 if 的条件中时,被转成布尔值,而 Boolean([0])为 true,所以就进行下一步判断 a == true,在进行比较时,[0]被转换成了 0,所以 0==true 为 false
数组从非 primitive 转为 primitive 的时候会先隐式调用 join 变成“0”,string 和 boolean 比较的时候,两个都先转为 number 类型再比较,最后就是 0==1 的比较了
js
var a = [1];
if (a) {
console.log(a == true);
} else {
console.log(a);
}
// true
!![]; //true 空数组转换为布尔值是 true,
!![0][0] == true; //true 数组转换为布尔值是 true //false 数组与布尔值比较时却变成了 false
Number([]); //0
Number(false); //0
Number(["1"]); //1
2)所以当 a 出现在 if 的条件中时,被转成布尔值,而 Boolean([0])为 true,所以就进行下一步判断 a == true,在进行比较时,js 的规则是:
① 如果比较的是原始类型的值,原始类型的值会转成数值再进行比较
js
1 == true; //true 1 === Number(true)
"true" == true; //false Number('true')->NaN Number(true)->1
"" == 0; //true
"1" == true; //true Number('1')->1
② 对象与原始类型值比较,对象会转换成原始类型的值再进行比较。
③undefined 和 null 与其它类型进行比较时,结果都为 false,他们相互比较时结果为 true。
代码输出 - 52
js
const value = "Value is" + !!Number(["0"]) ? "yideng" : "undefined";
console.log(value);
答案
txt
yideng
- 优先级大于 ?
代码输出 - 53
js
const a = [1, 2, 3],
b = [1, 2, 3],
c = [1, 2, 4],
d = "2",
e = "11";
console.log([a == b, a === b, a > c, a < c, d > e]);
答案
txt
[false, false, false, true, true]
代码输出 - 54
js
console.log(null == 0);
console.log(null <= 0);
console.log(null < 0);
答案
txt
false
true
false
null>0 //null 转化为 number,为 0,所以 0>0 结果为 false。
null>=0 //null 转化为 number,为 0>=0,所以结果为 true。
null==0// null 在做相等判断时,不进行转型,所以 null 和 0 为不同类型数据,结果为 false
代码输出 - 55
js
let a = [];
let b = "0";
console.log(a == 0);
console.log(a == !a);
console.log(b == 0);
console.log(a == b);
答案
txt
true
true
true
false
/**
对象到数字的转换过程:
1.如果对象具有valueof()方法,后者返回一个原始值,则JavaScript将这个原始值转换为数字并返回;
2.否则,如果对象具有toString()方法,后者返回一个原始值,JavaScript将这个字符串转换为数字并返回;
3.否则,报错。
4.数组继承了默认的valueOf()方法,但是数组、函数和正则表达式调用此方法后,只返回对象本身,因此转换为数字,还会继续调用toString()方法,空数组调用toString()返回空字符串,转换为数字为0,
*/
let a = [];
let b = "0";
console.log(a == 0);
// == 运算符,一边为对象,一边数字,对象到数字的转换过程,0==0 返回true
console.log(a == !a);
// 逻辑非,如果为对象,返回false,布尔值转换为数字 0,对象到数字的转换过程,0==0 返回true
console.log(b == 0);
// 字符串转换为数字 ,返回true
console.log(a == b);
// 对象到数字的转换过程1、2 返回 ‘’ ,‘’==‘0’ false
代码输出 - 56
js
var obj = {};
var x = +obj.yideng?.name ?? "京程一灯";
console.log(x);
答案
txt
NaN
+undefined -> NaN
代码输出 - 57
js
function createIncrement() {
let count = 0;
function increment() {
count++;
}
let message = `Count is ${count}`;
function log() {
console.log(message);
}
return [increment, log];
}
const [increment, log] = createIncrement();
increment();
increment();
increment();
log();
答案
txt
Count is 0
代码输出 - 58
js
parseInt(1 / 0, 19);
答案
txt
- parseInt(..)先将参数强制类型转换为字符串再进行解析,这样做没有任何问题。因为传递错误的参数而得到错误的结果,并不能归咎于函数本身。
- parseInt(1/0, 19)实际上是 parseInt("Infinity", 19)
- 19 有效数字为 0-9,a-i
- “Infinity” 有效字符为“i”,第二个字符不是有效字符,解析停止
- 最终结果为 18