+++第一题:
let num = parseFloat('width:100px'); //NaN
if (num == 100) { alert(1); }
else if (num == NaN) { alert(2); } //NaN!==NaN Object.is(NaN,NaN)===true
else if (typeof num == 'number') { alert(3); }
else { alert(4); }
答案:3
+++++基于parseInt(val)/parseFloat(val)数据转换
- val一定是一个字符串,如果不是则先转换为字符串
- 然后从字符串左侧第一个字符开始查找,遇到一个非有效数字字符则结束查找,把找到的有效数字字符串转换为数字,一个找不到结果则是NaN
+++第二题:
let result = 100 + true + 21.2 + null + undefined + "Tencent" + [] + null + 9 + false;
console.log(result);
答案:"NaNTencentnull9false"
+++++把其它数据类型隐式转换为number类型:Number([val])
- 字符串:空字符串转换为0,只要字符串出现非有效数字,结果就是NaN
- 布尔类型:true -> 1 false -> 0
- null - > 0 undefined -> NaN
- symbol会报错 BigInt会去掉'n'(可能出现科学计数法)
- 对象:Symbol.toPrimitive -> valueOf -> toString -> 转换为数字
+++第三题:
var a = { n: 1 };
var b = a;
a.x = a = { n: 2 };
console.log(a.x);
console.log(b);
答案:undefined {n:1,x:{n:2}}
+++++解析:
var a={n:1}此时的a为一个对象用0x000来代表其堆内存地址,则a=0x000 {n:1}
var b=a此时b的空间地址也是0x000
a.x=a={n:2}考察的为连等式从右向左开始,例如:a=b=13 => b=13 a=13但是此时的a.x成员访问优先级很高,所以a.x=0x001 {n:2} a=0x001 {n:2};
因为是先执行的a.x=0x001 {n:2},所以空间地址0x000的内容发生变化,成为了{n:1,x:0x001}==>{n:1,x:{n:2}}
当代码执行到console.log(a.x)的时候a所指向的空间地址为0x001 {n:2}里边没有x这个属性,所以结果为undefined.
当代码执行到console.log(b)的时候b所指向的空间地址依旧为0x000,但是内容变成了{n:1,x:0x001}==>{n:1,x:{n:2}}
+++第四题:
a等于什么值会使条件成立「两种方案」
var a = ?;
a.toString = a.shift;
if (a == 1 && a == 2 && a == 3) {
console.log('OK');
}
+++++方案一:利用 对象==数字 会把对象转换为数字「我们重写转换的某一步骤」
var a = {
i: 0
};
a[Symbol.toPrimitive] = function () {
return ++this.i;
};
if (a == 1 && a == 2 && a == 3) {
console.log('OK');
}
var a = [1, 2, 3];
a.toString = a.shift;
if (a == 1 && a == 2 && a == 3) {
console.log('OK');
}
+++++方案二:利用 Object.defineProperty 实现数据劫持
var i = 0;
Object.defineProperty(window, 'a', {
get() {
return ++i;
}
});
if (a == 1 && a == 2 && a == 3) {
console.log('OK');
}
+++第五题:
let arr = [27.2, 0, '0013', '14px', 123];
arr = arr.map(parseInt);
console.log(arr);
答案:[27,NaN,1,1,27]
+++++parseInt([val],[radix])
- [radix]不写或者是0:则默认是十进制「如果val字符串是以0x开始的,则默认值是16」
- [radix]有效范围2~36,不再这个范围内,则结果是NaN
- 从[val]字符串右侧第一个字符查找,找到所有符合[radix]进制的值(遇到一个不符合的则结束查找),最后转换为10 进制的数字
- 把其它进制转换为十进制:按权展开求和
arr.map(parseInt)==>map(function(item.index))==>map(parseInt(item,index))所以返回数组的第一项可以看做是parseInt('27.2',0) -> parseInt('27.2',10)==>27;
第二项:parseInt(0,1)因为[radix]的取值范围是2~36,所以第二项为NaN
第三项:parseInt('0013',2) -> '001' 0*2^2+0*2^1+1*2^0==>1
第四项:parseInt('14px',3) -> '1' 1*3^0==>1
第五项:parseInt(123,4) -> parseInt('123',4) '123' 1*4^2+2*4^1+3*4^0=>16+8+3+>27
+++第六题:
/*下⾯代码是否可以,每隔1000MS依次输出 0 1 2 3 4 5 ? 如果不可以,说明为啥?以及如何解决?「三种⽅案处理」 */
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, (i + 1) * 1000);
}
答案:不可以,因为var不会产生块级上下文,所以代码都在全局上下文中执行的;循环是同步任务、定时器是异步任务;先执行同步任务再执行异步任务,执行完循环之后全局的i已经变为了5,所以这样输出的结果都是5
+++++解决方案一:闭包
for (var i = 0; i < 5; i++) {
// 第一轮 全局i=0
// 第一轮 全局i=1
// ...
// 全局i=5 循环结束
(function (i) {
// 闭包1 私有i=0
// 闭包2 私有i=1
// ...
setTimeout(function () {
// 找的是对应闭包中的i
console.log(i);
}, (i + 1) * 1000);
})(i);
}
+++++解决方案二:let的原来还是闭包
for(let i=0;i<5;i++){
setTimeout(function(){
console.log(i)
},(i+1)*1000);
}
+++++解决方案三:setTimeout(回调函数,时间,给回调函数预传递的实参值「当定时器到达事件,执行回调函数的时候,可以获取到预先传递的值,其实定时器内部也是基于“闭包”实现的!」)
for(var i=0;i<5;i++){
setTimeout(function(){
console.log(i);
},(i+1)*1000,i);
}
+++第七题:
let a = 0,
b = 0;
function A(a) {
A = function (b) {
alert(a + b++);
};
alert(a++);
}
A(1);
A(2);
答案:1 4
+++第八题:
console.log(foo);
{
console.log(foo);
function foo() { }
foo = 1;
console.log(foo);
}
console.log(foo);
答案:undefined 函数foo(){} 1 函数foo(){}
第九题:
let x = 5;
function fn(x) {
return function (y) {
console.log(y + (++x));
}
}
let f = fn(6);
f(7);
fn(8)(9);
f(10);
console.log(x);
答案: 14 18 18 5
+++第十题:
const curring = function curring() {
//编写代码实现一下输出
};
let add = curring();
let res = add(1)(2)(3);
console.log(+res); //->6
add = curring();
res = add(1, 2, 3)(4);
console.log(+res); //->10
add = curring();
res = add(1)(2)(3)(4)(5);
console.log(+res); //->15
+++++答案及解析:
// curring:柯理化函数,闭包的应用「保存应用」
const curring = function curring() {
let arr = [];
const add = (...params) => {
// 把每一次add传递的值都保留起来
arr = arr.concat(params);
// 每一次add执行,保证其返回的还是add:这样就可以一直执行下去了
return add;
};
add[Symbol.toPrimitive] = () => {
// 把每一次传递进来的值,基于数组求和,计算出最后的结果
return arr.reduce((res, item) => res + item, 0);
};
return add;
};
+++第十一题:
// 实现plus和minus,完成以下输出
let n = 10;
let m = n.plus(10).minus(5);
console.log(m);//=>15(10+10-5)
+++++ n数字是Number类的实例,可以访问Number.prototype上的方法「基于内置类原型扩展方法」 n.push(10) 方法中的this -> n num ->10
const checkNum = num => {
num = +num;
if (isNaN(num)) return 0;
return num;
};
Number.prototype.plus = function plus(num) {
num = checkNum(num);
return this + num;
};
Number.prototype.minus = function minus(num) {
num = checkNum(num);
return this - num;
};
第十二题:
function Foo() {
getName = function () { console.log(1); };
return this;
}
Foo.getName = function () { console.log(2); };
Foo.prototype.getName = function () { console.log(3); };
var getName = function () { console.log(4); };
function getName() { console.log(5); }
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
答案:2 4 1 1 2 3 3
+++第十三题:重写内置new的方法
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function () { console.log('wangwang'); }
Dog.prototype.sayName = function () { console.log('my name is ' + this.name); }
function _new(ctor) {
//=>完成你的代码
}
let sanmao = _new(Dog, '三⽑');
sanmao.bark(); //=>"wangwang"
sanmao.sayName(); //=>"my name is 三⽑"
console.log(sanmao instanceof Dog); //=>true
+++++内置new的方法
function _new(ctor) {
// 4.对ctor的格式进行校验:必须是一个函数,但是还不能是Symbol/BigInt
if (typeof ctor !== "function") throw new TypeError("ctor is not a constructor");
if (ctor === Symbol || ctor === BigInt) throw new TypeError("Symbol/BigInt is not a constructor");
// 1.创建一个实例对象「空对象、对象.__proto__===Dog.prototype」
var obj = Object.create(ctor.prototype);
// 2.像普通函数一样执行,需要让函数中的this指向创建的实例对象
var params = [].slice.call(arguments, 1);
var result = ctor.apply(obj, params);
// 3.检测函数执行的返回值,如果返回的是原始值类型的值,则默认把实例对象返回
if (obj !== null && /(object|function)/i.test(typeof result)) return result;
return obj;
}
+++第十四题:
let obj = {
2: 3,
3: 4,
length: 2,
push: Array.prototype.push
};
obj.push(1);
obj.push(2);
console.log(obj);
答案:{2: 1, 3: 2, length: 4, push: ƒ}
+++++鸭子类型
obj是个“伪/类数组”,不能直接使用数组原型上的方法,如果想使用
- 调用数组原型上的方法,让方法中的this指向类数组,实现方法的借用,例如:
[].slice.call(arguments) - 把需要借用的方法赋值给对象的私有属性
- 把类数组转化为数组,例如:
Array.from([value])
此题需要了解push方法的内置操作
Array.prototype.push = function push(val) {
// this->arr val->10
this[this.length]=val;
this.length++;
return this.length;
};
arr.push(10);
通过上方的代码我们就可以清除的了解到这道题中obj.push(1)的一个大概过程:
let obj = {
2: 3, //属性值3就变成了1
3: 4, //属性值3就变成了2
length: 2,//length经过两次的obj.length++变为了4
push: Array.prototype.push
};
obj.push(1);
//this->obj val->1
//=> obj[obj.length]=1; => obj[2]=1;
//=> obj.length++;
obj.push(2);
//this-> obj val->2
//=> obj[obj.length]=2; => obj[3]=2;
//=> obj.length++;
console.log(obj);
+++第十五题:基于JS重写内置call方法
Function.prototype.call = function call(context, ...params) {
// this->fn context->obj params->[10, 20]
let key = Symbol("KEY"),
result;
// @4 对context做格式校验:要求context必须是个对象类型值、如果是null/undefined则让其为window
if (context == null) context = window;
if (!/(object|function)/i.test(typeof context)) context = Object(context);
// @1 给context设置一个属性,让其属性值等于要被执行的方法“this”「设置的属性名,不要和现有对象的属性名发生冲突:symbol类型的属性即可」
context[key] = this; //Reflect.set(context, key, this);
// @2 让 context.xxx 执行,这样即把方法执行了,同时也让this改为context了
result = context[key](...params);
// @3 把新设置的属性移除,并且把函数执行的结果返回
delete context[key]; //Reflect.deleteProperty(context, key);
return result;
};
+++++想要重写内置call就要了解需求
const fn = function fn(x, y) {
console.log(this, x, y);
};
let obj = {
name: 'zhufeng'
};
// obj.fn = fn;
// obj.fn(10, 20);
fn.call(obj, 10, 20);
- 把
fn执行,让方法中的this指向obj,并且传递10/20 - 让方法执行,让方法中的this指向某个对象,给THIS对象设置一个属性,让其属性值是要执行的函数即可
+++第十六题:基于JS重写内置bind方法
Function.prototype.bind = function bind(context, ...params) {
// this->fn context->obj params->[10,20]
let self = this;
return function (...args) {
// this->body args->[ev]
self.call(context, ...params.concat(args));
};
};
+++++重写内置bind的需求:点击body执行fn,让fn中的this指向obj,并且分别传递10/20/事件对象
const fn = function fn(x, y, ev) {
console.log(this, x, y, ev);
};
let obj = {
name: 'zhufeng'
};
let body = document.body;
// body.onclick = fn; //this->body x->事件对象 y/ev->undefined
// body.onclick = fn.call(obj, 10, 20); //这样会立即把fn执行,等不到点击操作,且把fn执行的结果赋值给事件绑定,body.onclick=undefined!
// 思路:先给事件绑定匿名函数,当点击的时候执行匿名函数;在匿名函数执行的时候,我们基于call把fn执行,实现我们想要的需求即可!!
// body.onclick = function (ev) {
// // this->body ev->事件对象
// fn.call(obj, 10, 20, ev);
// };
body.onclick = fn.bind(obj, 10, 20);
+++第十七题:检测是否为纯对象:标准普通对象「proto===Object.prototype」、Object.create(null)
const hasOwn = Object.prototype.hasOwnProperty;
const isPlainObject = function isPlainObject(obj) {
let proto, Ctor;
// 如果obj是null或者undefined,再或者检测数据类型的结果不是对象,则obj一定不是纯粹对象
if (!obj || Object.prototype.toString.call(obj) !== "[object Object]") return false;
// 获取obj指向的原型对象
proto = Object.getPrototypeOf(obj);
// 匹配 Object.create(null)
if (!proto) return true;
// 有原型对象则检测其是否为Object.prototype
Ctor = hasOwn.call(proto, 'constructor') && proto.constructor;
return typeof Ctor === "function" && Ctor === Object;
};
+++第十八题:实现数组和对象的深拷贝deepClone
++++@1 JSON.parse(JSON.stringify(obj))先转换为字符串,再把字符串转换为对象,这样涉及的所有内存空间都会重新的开辟,实现深拷贝的效果!
- 处理不了BigInt类型值
TypeError:Do not know how to serialize a BigInt symbol/undefined/function类型的值会丢失
- 正则和错误对象会变为
{} - 日期对象会直接变为字符串,就转不回来了
- ...
原因:JSON.stringify无法对这些类型的值进行处理「可以正常处理的:数字、字符串、普通对象、数组对象、null等格式」
const hasOwn = Object.prototype.hasOwnProperty;
const isPlainObject = function isPlainObject(obj) {
let proto, Ctor;
if (!obj || Object.prototype.toString.call(obj) !== "[object Object]") return false;
proto = Object.getPrototypeOf(obj);
if (!proto) return true;
Ctor = hasOwn.call(proto, 'constructor') && proto.constructor;
return typeof Ctor === "function" && Ctor === Object;
};
// 实现对数组和对象的迭代
const each = function each(obj, callback) {
if (typeof callback !== "function") throw new TypeError("callback is not a function");
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
let item = obj[i];
let res = callback(item, i);
if (res === false) break;
}
}
if (isPlainObject(obj)) {
let keys = Reflect.ownKeys(obj);
for (let i = 0; i < keys.length; i++) {
let key = keys[i],
value = obj[key];
let res = callback(value, key);
if (res === false) break;
}
}
return obj;
};
const deepClone = function deepClone(obj, already) {
// 预防死递归
if (typeof already === "undefined") already = [];
if (already.includes(obj)) return obj;
already.push(obj);
// 如果obj是原始值类型(或函数),则直接返回对应的值即可
if (obj == null || !/object/i.test(typeof obj)) return obj;
let isArray = Array.isArray(obj),
isObject = isPlainObject(obj),
ctor = obj.constructor,
newObj;
if (!isArray && !isObject) {
// 既不是数组也不是纯粹的对象:创造相同构造函数的不同实例即可
return new ctor(obj);
}
// 是数组或者对象:则循环进行克隆「递归,只要符合条件,都需要处理」
newObj = new ctor();
each(obj, (value, key) => {
newObj[key] = deepClone(value, already);
});
return newObj;
};
+++第十九题:
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');
答案:'sctipt start' 'async1 start' 'async2' 'promise1' 'script end' 'async1 end' 'promise2' 'steTimeout'
+++第二十题:
let body = document.body;
body.addEventListener('click', function () {
Promise.resolve().then(() => {
console.log(1);
});
console.log(2);
});
body.addEventListener('click', function () {
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);
});
答案:2 1 4 3
+++第二十一题:
console.log('start');
let intervalId;
Promise.resolve().then(() => {
console.log('p1');
}).then(() => {
console.log('p2');
});
setTimeout(() => {
Promise.resolve().then(() => {
console.log('p3');
}).then(() => {
console.log('p4');
});
intervalId = setInterval(() => {
console.log('interval');
}, 3000);
console.log('timeout1');
}, 0);
答案:'start' 'p1' 'p2' 'timeout1' 'p3' 'p4' 'interval'每三秒打印一次
+++第二十二题:实现一个sleep函数的定义,让sleep的功能setTimeout类似,但是是promise风格的使用方式
function sleep(time) {
// 具体实现代码
}
sleep(2000).then(function () {
console.log('logged after 2 seconds.');
});
答案:
const sleep = function sleep(time) {
time = +time || 0;
if (time < 0) time = 0;
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, time);
});
};
拓展:不能使用循环的方式实现每隔一秒打印出的数字累加1
(async function () {
console.log(1);
await sleep(1000);
console.log(2);
await sleep(1000);
console.log(3);
await sleep(1000);
console.log(4);
})();