1、数据类型
// number数字类型:NaN 非有效数字、Infinity 无穷大的值
// console.log(Number.MAX_SAFE_INTEGER); //9007199254740991 最大安全数字
// Number.MIN_SAFE_INTEGER 最小安全数字
// NaN!==NaN isNaN([value]):检测[value]是否为有效数字
// Object.is(NaN,NaN):这样的结果是TRUE
// string字符串类型:'' 、 "" 、 `` 包起来的都是字符串
// boolean布尔类型:true、false
// null
// undefined 吴签{男},他的男朋友是undefined、他的女朋友是null
// symbol唯一值类型
// console.log(Symbol() === Symbol()); //false 创建了两个唯一值
// console.log(Symbol('AA') === Symbol('AA')); //false
// @1情况一:给对象设置唯一的属性「对象的属性名类型:字符串 & Symbol类型」
/* let obj = {
name: 'zhufeng',
age: 12,
[Symbol()]: 100
};
console.log(obj[Symbol()]); //undefined 获取的时候,是获取一个新的唯一值的属性值
*/
/* let key = Symbol();
let obj = {
name: 'zhufeng',
age: 12,
[key]: 100
};
console.log(obj[key]); //100 */
/* let obj = {
name: 'zhufeng',
age: 12,
[Symbol()]: 100
};
let symbolKeys = Object.getOwnPropertySymbols(obj); //获取当前对象所有Symbol类型的私有属性,结果数组
symbolKeys.forEach(key => {
console.log(obj[key]);
}); */
/* let a = {
name: 'zhufeng'
};
let b = {
name: 'web'
};
let obj = {};
obj[a] = 100; //obj["[object Object]"]=100
obj[b] = 200; //obj["[object Object]"]=200
console.log(obj[a]); //200 */
// @2 redux/vuex公共状态管理的时候,派发的行为标识就可以基于Symbol类型进行宏管理
// @3 Symbol.hasInstance\Symbol.toStringTag\Symbol.toPrimitive\Symbol.iterator...很多JS底层的处理机制,就是基于这些属性方法实现的
// BigInt大数类型:数字后面加个n即使大数类型
// + 10 Number类型
// + 10n BigInt类型
// @1 JS中在进行数学运算的时候,如果计算的数值超过最大/最小安全数字,计算出来的结果很可能是不准确的「如果遇到这样的需求,则一定会出问题:服务器端数据库存储,是有longInt类型,服务器返回给客户端的值也是超过安全数字的{一般会以字符串的方式返回,这样保证返回途中数字的安全性},此时我们去进行数学运算,结果可能就”崩“了」
// 解决办法:把服务器返回的值变为bigint格式的,然后进行运算「保证了运算的准确性{进行运算的另外一个值也应该是bigint类型的}」;把运算的结果,再次变为字符串,发送给服务器即可!!
// 对象类型
// + 标准普通 {num:100}
// + 标准特殊 [10,20] /\d+/ new Date() new Error() ...
// + 非标准特殊 new Number(10) ... 原始值类型对应的对象类型实例
// + 函数对象 function fn(){}
//=======================
// 检测数据类型
// + typeof
// + 返回结果是一个字符串,字符串中包含了对应的数据类型
// + typeof检测未被声明的变量,不会报错,结果是”undefined“
// + 封装中会用到
// + typeof null -> "object" ?
// + typeof 不能细分是啥对象「排除函数对象」,返回结果是 ”object/function“ ?
// 所有的数据类型值,在计算机底层中都是以2进制形式存储的{64位}
// 而typeof就是基于二进制检测的:它把000开始的都识别为对象,而null全是0,所以也被识别为对象了...但是内部识别为对象后,会再次检测这个对象有没有内部实现[[call]],如果实现了,结果是”function“,没有实现就是”object“....
// 好处:检测处理的性能是很高的
// + instacnceof
// + constructor
// + Object.prototype.toString.call([value])
// ----
// + Array.isArray([value])
// + ...
// 底层机制「优缺点」 -> 重写/面试题 -> JQ源码/Lodash源码 -> 自己写一套直接项目中使用的检测方法库
/* // 需求:验证 val 是否为一个对象
if (val !== null && /^(object|function)$/i.test(typeof val)) {
// ...
} */
/*
// 支持浏览器导入 && 支持NODE端运行{CommonJS规范}
(function () {
let utils = {
// ...
};
/!* 暴露API *!/
if (typeof module === "object" && typeof module.exports === "object") module.exports = utils;
if (typeof window !== "undefined") window.utils = utils;
})();
*/
2、堆栈以及变量的存储
3、Symbol
/* xxx[Symbol.toPrimitive](hint){
// hint:'number' / 'string' / 'default'
// + number:获取当前对象的数字类型的原始值
// + string:获取当前对象的字符串类型的原始值
// + default:根据操作获取数字或者字符串类型的原始值
} */
/* let obj = {
name: 'zhufeng',
age: 12,
[Symbol.toPrimitive](hint) {
let result;
switch (hint) {
case 'number':
result = 0;
break;
case 'string':
result = JSON.stringify(obj);
break;
default:
result = "";
}
return result;
}
};
console.log(Number(obj)); // hint:"number"
console.log(String(obj)); // hint:"string"
console.log(10 + obj); // hint:"default"
console.log(10 - obj); // hint:"number" */
//===================
/*
parseInt([val {string}], [radix])
+ 在[val]字符串中,从左到右找到符合[radix]进制的内容,然后把这些内容当做[radix]进制,转换为10进制的数字
+ [radix]的范围:2~36 不在这个范围内,最后结果都是NaN
+ [radix]不写或者写零:默认值是10「特殊:如果[val]字符串是以0x开始的,默认值是16」
*/
/* console.log(parseInt('10px')); //=>10
// parseInt('10px',10)
// 在字符串中找到所有符合10进制的内容 -> '10'
// 把找到的 '10' 看做十进制转换为十进制 => 10 */
/* console.log(parseInt('1030px', 2));
// 在字符串中,找到所有符合二进制的内容 -> '10'
// 把'10'看做二进制,转换为十进制? 如何把其它机制的值转换为十进制 “按权展开求和”
// 个位数权重0 十位数权重1 百位数权重2 ...
// 1*2^1 + 0*2^0 => 2 */
/* let arr = [27.2, 0, '0013', '14px', 123];
arr = arr.map(parseInt);
console.log(arr); */
/*
arr[0] = parseInt(27.2,0);
parseInt('27.2',10)
找到符合十进制的内容 -> '27'
把'27'看做十进制转换为十进制 => 27
arr[1] = parseInt(0,1);
parseInt('0',1) => NaN 一进制不在进制范围内
arr[2] = parseInt('0013',2);
找到所有符合二进制的内容 -> '001'
把'001'看做二进制转换为十进制
0*2^2 + 0*2^1 + 1*2^0 => 1
arr[3] = parseInt('14px',3);
找到所有符合三进制的内容 -> '1'
把'1'看做三进制转换为十进制
1*3^0 => 1
arr[4] = parseInt(123,4);
parseInt('123',4)
找到所有符合四进制的内容 -> '123'
把'123'看做四进制转换为十进制
1*4^2 + 2*4^1 + 3*4^0
16 + 8 + 3 => 27
*/
//=====================
// 把对象obj转换为字符串
// + String(obj):Symbol.toPrimitive -> valueOf -> toString 浏览器默认隐式转换用的是String(obj)
// + obj.toString() :直接调用这个方法转字符串,不会在执行以上的规则
//----------
// “+”有两边,其中一边如果是对象obj,则会
// @1 调用obj[Symbol.toPrimitive]('default')
// @2 没有这个属性,则再次调用valueOf
// @3 valueOf获取的不是原始值,则继续toString,此时获取的结果是字符串,“+”就变为字符串拼接了
/* console.log(10 + [10]);
// 没有Symbol.toPrimitive -> valueOf获取的也不是原始值 -> 调用toString "10" => "1010"
console.log(10 + {});
// 没有Symbol.toPrimitive -> valueOf获取的也不是原始值 -> 调用toString "[object Object]" => "10[object Object]"
console.log(10 + new Date());
// 调用日期的Symbol.toPrimitive('default') => "10Sun Jul 25 2021 11:28:37 GMT+0800 (中国标准时间)"
console.log(10 + new Number(10));
// 没有Symbol.toPrimitive -> valueOf 10 => 20
console.log(10 + new String('10'));
// 没有Symbol.toPrimitive -> valueOf "10" => "1010" */
// “+”有两边,其中一边是字符串,妥妥的字符串拼接
// “+”有两边,剩下的情况一般都是数学运算了
//----------
// “+”只有一边,例如:+xxx
// 一般都是把xxx转换为数字「基于:Number(xxx)」
/* let result = 100 + true + 21.2 + null + undefined + "Tencent" + [] + null + 9 + false;
// 122.2 + undefined = NaN
// NaN + "Tencent" = "NaNTencent"
// 在往后都是字符串拼接
console.log(result); // "NaNTencentnull9false" */
//=====================
// 把其它类型值转换为布尔,只有 “0/NaN/空字符串/null/undefined” 五个值是false,其余都是true「哪怕是把对象转换为布尔,也不再调用Symbol.toPrimitive那套机制了,直接看是否是五个中的一个即可」
/* console.log([] == false);
// 都转换为数字 Number([]) => 0 Number(false) => 0
// TRUE
console.log(![] == false);
// 需要先算 ![] => false
// false == false => TRUE */
// 解决方案一:利用 == 比较的时候,会把对象转换为数字 Number(a)
// + Symbol.toPrimitive
// + valueOf
// + toString
// + 把字符串变为数字
/*
var a = {
i: 0
};
a[Symbol.toPrimitive] = function toPrimitive() {
// this -> a
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');
}
*/
// 解决方案二:在全局上下文中,基于 var/function 声明的变量,并不是给VO(G)设置的全局变量「基于let/const声明的变量才是」,而是给GO(window)全局对象设置的属性 var a=? ==> window.a=?
// 我们基于数据劫持完成对应的操作
var i = 0;
Object.defineProperty(window, 'a', {
get() {
return ++i;
}
});
if (a == 1 && a == 2 && a == 3) {
console.log('OK');
}
4、全局变量以及局部变量
/*
GO:全局对象「堆内存中分配的一块空间 0x000」,存储浏览器的内置API
x -> 12
z -> 14
EC(G)全局执行上下文
VO(G)全局变量对象:存储全局上下文中声明的变量的{排除基于var/function声明的}
window ---> 0x000
y -> 13
*/
// console.log(a); //首先会到VO(G)查找,看是否为全局变量,如果不是,则再去GO中找,看是否为全局对象的一个属性,如果还不是,则报错 Uncaught ReferenceError: a is not defined
// console.log(window.a); //直接去GO中查找是否存在a这个成员,如果没有则不会报错,值是undefined
/*
debugger;
var x = 12;
let y = 13;
z = 14; //window.z=14;
console.log(x, y, z); //12,13,14
console.log(window.x, window.y, window.z); //12 undefined 14
*/
//======================
let x = [12, 23];
const fn = function fn(y) {
y[0] = 100;
y = [100];
y[1] = 200;
console.log(y);
};
fn(x);
console.log(x);
5、全局变量以及局部变量2
/*
EC(G)
VO(G) 或者 GO
a -> 12
变量提升:
var a;
*/
/*
console.log(a); //undefined
var a = 12;
console.log(b); //Uncaught ReferenceError: Cannot access 'b' before initialization
let b = 12;
//==只有带var/function存在变量提升,带let/const的不存在,所以不能再声明之前使用这个变量「体现出ES6这版本的语法规范更加的严谨」
*/
/*
EC(G)
VO(G)/GO
fn -> 0x001 [[scope]]:EC(G)
-> 0x002 [[scope]]:EC(G)
-> 12
变量提升
function fn(){ console.log(1); } 声明+定义{赋值}
var fn; 声明
function fn(){ console.log(2); } 声明+定义{赋值}
*/
/* console.log(fn); //0x002
function fn(){ console.log(1); } //变量提升阶段已经处理过了,直接跳过即可
console.log(fn); //0x002
var fn = 12;
console.log(fn); //12
function fn(){ console.log(2); } //跳过
console.log(fn); //12 */
/* // 因为变量的提升的机制,我们可以把函数执行放在创建函数的代码之前「不严谨」
fn();
function fn() {
console.log(1);
} */
/* // 真实项目中,推荐大家使用函数表达式「逻辑更加严谨」
// fn(); //Uncaught ReferenceError: Cannot access 'fn' before initialization
const fn = function fn() {
console.log(1);
};
fn(); */
/*
EC(G)
VO(G)/GO
a => window.a=undefined
变量提升:
var a;
*/
/* console.log(a); //undefined
if (!('a' in window)) { // attr in obj:检测attr是否为obj对象的一个属性(成员),如果是对象的属性,结果是true
// 'a' in window => true
var a = 13;
}
console.log(a); //undefined */
6、函数的变量提升
/* //忽略报错的影响
console.log(a);
// console.log(b);
var a = 12;
let b = 13;
if (1 == 1) {
console.log(a);
// console.log(b);
var a = 100;
let b = 200;
console.log(a);
console.log(b);
}
console.log(a);
console.log(b); */
// 函数是个渣男
/* debugger;
console.log(foo);
if (1 === 1) {
console.log(foo);
function foo() {}
foo = 1;
console.log(foo);
}
console.log(foo); */
/*
EC(G)
VO(G) / GO
f -> 0x000 [[scope]]:EC(G) “return true”
g -> 0x001 [[scope]]:EC(G) “return false”
变量提升:- -
*/
/* f = function () {return true;};
g = function () {return false;};
(function () {
/!*
EC(AN)
AO(AN)
g
作用域链:<EC(AN),EC(G)>
初始THIS:window / undefined
初始ARG:...
形参赋值:--
变量提升:function g; 只声明不定义了「因为其出现在判断体中」
*!/
if (g() && [] == ![]) { //Uncaught TypeError: g is not a function
f = function () {return false;}
function g() {return true;}
}
})();
console.log(f());
console.log(g()); */
6、var与let
/* let x = 5;
const fn = function fn(x) {
return function (y) {
console.log(y + (++x));
}
};
let f = fn(6);
f(7);
fn(8)(9);
f(10);
console.log(x); */
/* let a = 0,
b = 0;
let A = function A(a) {
A = function (b) {
alert(a + b++);
};
alert(a++);
};
A(1);
A(2); */
/*
LET 和 VAR 的区别?
@1 变量提升: let不存在变量提升、而var是具备变量提升的
@2 重复声明: let不允许重复声明「当前上下文中,不论当前变量基于何种方式声明过,都不允许再用let声明了」,一但重复声明,AST词法阶段都过不了,啥代码都不会执行;var不会重复声明,但是不会报错;
@3 和GO的关系: 在全局上下文中,基于var/function声明的变量是放在GO中的「可以基于window.xxx去访问、也可以直接获取」;但是基于let/const声明的变量是放在VO(G)中的,和GO没关系!!
@4 块级上下文: 在除函数/对象的大括号外,如果括号中出现let/const/function会产生块级私有上下文,而且声明的变量也是块中的私有变量;但是var既不会产生块上下文,块上下文也不会对其产生影响!!
@5 暂时性死去问题
LET 和 CONST 的区别?
LET声明的是变量「对」
CONST声明的是常量「不准确」:CONST声明的也是变量,只不过不允许重新关联其它的值(不允许指针重新指向)
*/
/* const obj = {
name: 'zhufeng'
};
obj.name = '哈哈哈';
console.log(obj); //{name:'哈哈哈'} */
// const m = 20;
// m = 30; //Uncaught TypeError: Assignment to constant variable. 不允许重新关联
// const m; //Uncaught SyntaxError: Missing initializer in const declaration 必须赋值初始值
/*
// console.log(typeof n); //undefined 基于typeof检测一个未被声明的变量,结果不会报错,而是“undefined”
// console.log(typeof n); //Uncaught ReferenceError: Cannot access 'n' before initialization
// let n = 20;
*/
/* // 在词法分析阶段报错 Uncaught SyntaxError: Identifier 'n' has already been declared
console.log('OK');
let n = 10;
var n = 20; */
/* // console.log(n); //Uncaught ReferenceError: Cannot access 'n' before initialization 词法分析阶段,我们就知道未来在全局上下文中会基于let声明一个n的变量,所以此时报错是:不允许在声明之前使用他...
console.log(m); //undefined
let n = 10;
var m = 20; */
7、闭包初识
// 闭包:保护 & 保存
// 形成一个闭包(不被释放的上下文),我们就可以“预先”存储一些东西,而这些东西可以供其下级上下文“后期”调取使用 ->柯理化函数思想「预先存储的思想」
/* const fn = function fn(...params) {
// params:[1,2]
return function proxy(...args) {
// args:[3]
params = params.concat(args);
return params.reduce(function (result, item) {
return result + item;
});
};
}; */
/* const fn = (...params) => (...args) => params.concat(args).reduce((result, item) => result + item);
let res = fn(1, 2)(3);
console.log(res); //=>6 1+2+3 */
/* let arr = [10, 20, 30, 40];
// 数组中常用的迭代方法:forEach、map、filter、find、findIndex、every、some、reduce、reduceRight...
// reduce实现数组的迭代,并且可以实现结果的累计
// 第一轮:result 10 从数组第二项开始迭代 item=20 index=1 回调函数返回值30
// 第二轮:result 30(获取上一轮回调函数执行的返回结果) item=30 index=2 返回值60
// 第三轮:result 60 item=40 index=3 返回值100
// res等于最后返回的值 100
let res = arr.reduce((result, item, index) => {
return result + item;
});
// 第一轮 result=0 reduce第二个参数传递的值就是给result的初始值,如果不传,会把数组第一项作为初始值 , 接下来从数组第一项开始迭代 item=10 index=0
// ...
res = arr.reduce((result, item, index) => {
return result + item;
}, 0); */
//================
const curring = function curring() {
let params = [];
const add = (...args) => {
// 每一次执行函数都把传递的值存储起来
params = params.concat(args);
// 每一次执行函数都返回add,这样就可以一直无限次执行下去了
return add;
};
// 每一次输出add函数或者拿着个函数进行运算,都需要先把其转换为字符串/数字
add[Symbol.toPrimitive] = () => {
// 把每一次传递的值进行求和
return params.reduce((result, item) => result + item);
};
return add;
};
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
/* const fn = function fn(x, y) {
return x + y;
};
fn[Symbol.toPrimitive] = function () {
return 10;
};
// alert(fn); //先把fn变为字符串,然后再输出 String(fn) : Symbol.toPrimitive->valueOf->toString
// console.log(fn); //也是要把fn变为字符串再输出的,控制台会在字符串前面加一个 ƒ,代表这是函数字符串 「新版谷歌浏览器修改了机制:log输出也会执行Symbol.toPrimitive等方法,但是不论方法最后返回啥,还是输出的函数字符串」 */
//==========最原始的面试题,执行curring函数需要指定执行的次数的
/*
const curring = function curring(count) {
let params = [],
n = 0;
const add = (...args) => {
params = params.concat(args);
n++;
if (n >= count) {
// 求和
return params.reduce((result, item) => result + item);
}
return add;
};
return add;
};
let add = curring(3);
let res = add(1)(2)(3);
console.log(res); //->6
add = curring(2);
res = add(1, 2, 3)(4);
console.log(res); //->10
add = curring(5);
res = add(1)(2)(3)(4)(5);
console.log(res); //->15
*/
8、compose
/*
在函数式编程当中有一个很重要的概念就是函数组合, 实际上就是把处理数据的函数像管道一样连接起来, 然后让数据穿过管道得到最终的结果。 例如:
const add1 = (x) => x + 1;
const mul3 = (x) => x * 3;
const div2 = (x) => x / 2;
div2(mul3(add1(add1(0)))); //=>3
而这样的写法可读性明显太差了,我们可以构建一个compose函数,它接受任意多个函数作为参数(这些函数都只接受一个参数),然后compose返回的也是一个函数,达到以下的效果:
const operate = compose(div2, mul3, add1, add1)
operate(0) //=>相当于div2(mul3(add1(add1(0))))
operate(2) //=>相当于div2(mul3(add1(add1(2))))
简而言之:compose可以把类似于f(g(h(x)))这种写法简化成compose(f, g, h)(x),请你完成 compose函数的编写
*/
const add1 = x => x + 1;
const mul3 = x => x * 3;
const div2 = x => x / 2;
const compose = function compose(...funcs) {
let len = funcs.length;
if (len === 0) return x => x;
if (len === 1) return funcs[0];
return function operate(x) {
return funcs.reduceRight((result, item) => {
return typeof item === "function" ? item(result) : result;
}, x);
};
};
/* // redux源码中提供的compose函数
const compose = function compose(...funcs) {
if (funcs.length === 0) return arg => arg;
if (funcs.length === 1) return funcs[0];
// funcs:[div2, mul3, add1, add1]
// a:div2 b:mul3 -> x=>div2(mul3(x))
// a:x=>div2(mul3(x)) b:add1 -> x=>a(add1(x)) -> x=>div2(mul3(add1(x)))
// a:x=>div2(mul3(add1(x))) b:add1 -> x=>a(add1(x)) -> x=>div2(mul3(add1(add1(x))))
return funcs.reduce((a, b) => {
return x => {
return a(b(x));
};
});
}; */
// console.log(compose(div2, mul3, add1, add1)(0)); //=>3
// console.log(compose()(0)); //=>0
// console.log(compose(add1)(0)); //=>1
/* JS高阶编程技巧:惰性思想「懒」 */
// window.getComputedStyle([element],[?伪类]) //获取当前元素所有经过浏览器计算的样式对象「但凡这个元素经过浏览器渲染了,所有的样式都可以获取到{含:自己写的、浏览器默认的...}」
// getComputedStyle(box).width
// 不兼容IE6~8,在低版本浏览器中,需要使用 box.currentStyle.width
/* const css = function css(elem, attr) {
if ('getComputedStyle' in window) { // in 检测某个属性是不是对象的属性「不论私有还是公有」
return getComputedStyle(elem)[attr];
}
return elem.currentStyle[attr];
}; */
// 基于函数重构实现惰性思想
let css = function (elem, attr) {
if ('getComputedStyle' in window) {
css = function (elem, attr) {
return getComputedStyle(elem)[attr];
};
} else {
css = function (elem, attr) {
return elem.currentStyle[attr];
};
}
return css(elem, attr);
};
console.log(css(box, 'width'));
console.log(css(box, 'left'));
console.log(css(link, 'padding'));
// ... 打开页面后,当前某个方法会被执行1到多次
9、防抖截流初识
/* // 最简单的防抖处理:加标识判断即可
let submit = document.querySelector('#submit');
submit.isLoading = false;
submit.onclick = function () {
if (this.isLoading) return;
this.isLoading = true;
console.log(`数据请求发送...`);
setTimeout(() => {
console.log(`数据请求成功...`);
this.isLoading = false;
}, 5000);
}; */
const clearTimer = function clearTimer(timer) {
if (timer) clearTimeout(timer);
return null;
};
// 具备公共性的防抖函数处理:在用户频繁操作「频繁的规则自己设定」的场景中,我们只识别一次操作即可「识别第一次、识别最后一次」
const debounce = function debounce(func, wait, immediate) {
// 参数格式校验 & 默认值处理
if (typeof func !== 'function') throw new TypeError('func is not a function~');
if (typeof wait === 'boolean') immediate = wait;
if (typeof wait !== 'number') wait = 300;
if (typeof immediate !== "boolean") immediate = false;
let timer = null;
return function operate(...params) {
let now = !timer && immediate,
result;
timer = clearTimer(timer);
timer = setTimeout(() => {
// 让方法执行的时候,THIS是实参和没有debounce之前是一样的
// 最后执行「结束边界」
if (!immediate) func.call(this, ...params);
// 清除最后一次设定的定时器
timer = clearTimer(timer);
}, wait);
// 立即执行「开始边界」
if (now) result = func.call(this, ...params);
return result;
};
};
const handle = function handle() {
console.log(`数据请求发送...`);
setTimeout(() => {
console.log(`数据请求成功...`);
}, 3000);
};
submit.onclick = debounce(handle, 300, true);
/* // submit.onclick = function operate(ev) {
// 疯狂点击的按钮的时候,浏览器会疯狂的触发operate函数执行;我们只需要在operate中,控制handle只执行一次即可!!
// } */
10、封装防抖
const clearTimer = function clearTimer(timer) {
if (timer) clearTimeout(timer);
return null;
};
// 具备公共性的防抖函数处理:在用户频繁操作「频繁的规则自己设定」的场景中,我们只识别一次操作即可「识别第一次、识别最后一次」
const debounce = function debounce(func, wait, immediate) {
if (typeof func !== 'function') throw new TypeError('func is not a function~');
if (typeof wait === 'boolean') immediate = wait;
if (typeof wait !== 'number') wait = 300;
if (typeof immediate !== "boolean") immediate = false;
let timer = null;
return function operate(...params) {
let now = !timer && immediate,
result;
timer = clearTimer(timer);
timer = setTimeout(() => {
// 最后执行「结束边界」
if (!immediate) func.call(this, ...params);
timer = clearTimer(timer);
}, wait);
// 立即执行「开始边界」
if (now) result = func.call(this, ...params);
return result;
};
};
// 具备公共性的节流函数处理:在用户频繁操作的场景中,我们降低触发的频率
const throttle = function throttle(func, wait) {
if (typeof func !== 'function') throw new TypeError('func is not a function~');
if (typeof wait !== 'number') wait = 300;
let timer = null,
previous = 0;
return function operate(...params) {
let now = +new Date(),
remaining = wait - (now - previous),
result;
if (remaining <= 0) {
// 两次触发的间隔时间超过wait,我们让函数立即执行即可「例如:第一次...」
result = func.call(this, ...params);
// 记录当前触发的时间,作为下一次比较的上一次时间
previous = +new Date();
timer = clearTimer(timer);
} else if (!timer) {
// 如果没有设置过定时器,而且两次触发的间隔时间也不足wait,此时我们设置一个去等待执行即可
timer = setTimeout(() => {
func.call(this, ...params);
previous = +new Date();
timer = clearTimer(timer);
}, remaining);
}
return result;
};
};
const handle = function handle() {
console.log('OK');
};
// window.onscroll = handle; //默认的触发频率:浏览器最快的反应时间 「例如谷歌浏览器是5~7ms」
window.onscroll = throttle(handle);
/*
window.onscroll = function operate() {
//operate函数会在滚动中,间隔5~7ms触发一次;而在这个函数中,我们基于一些逻辑运算,控制handle间隔300ms触发一次!!
}
*/
// 谈谈你对闭包的理解「开放性问题」
// 把握住几个维度:概念「基础理论知识」、结合实战、高阶应用「含源码阅读」、插件/组件/类库封装
// 禁止背书式回答「技巧:设定一个场景,加一些铺垫词汇」
// 禁止事无巨细「技巧:只需要回答出核心关键,或者抛出一个“敏感”词汇,剩下的引导面试官去继续问自己」
// 孔子:温良恭俭让
// --------
// ***** 作业:“剧本精神” 把这个问题的话术进行整理,大概整理3min左右的,写下来,后期面试之前背一下...
11、面向对象
/*
面向对象:object oriented programming OOP编程/设计思想
+ JS
+ JAVA
+ Python
+ PHP
+ C#
+ C++
+ GO
+ ...
面向过程:procedure oriented programming POP
+ C语言
面向对象是基于类和实例来管理一门语言的;
@1 在JS中所有要学习和研究的内容都可以被称为“对象”、JS把其划分为很多类、我们平时就是创造这些类的实例进行开发的;
@2 每个实例之间有一些自己的私有属性和方法,也具备类赋予他们的公共的属性和方法;
@3 JS语言本身就是基于 “面向对象” 这种思想设计的语言,所以我们学习和开发也应该按照这种思想来进行处理!!
JS中的内置类
@1 数据类型所属内置类
+ Number 数字类 每一个数字都是他的一个实例「含NaN」
+ String
+ Boolean
+ Symbol
+ BigInt
+ Object
+ Function
+ Array
+ RegExp
+ Date
+ Error
+ ...
@2 DOM元素对象也有自己所属的内置类
+ 每一种HTML标签都有自己所属的内置类 HTMLDivElement / HTMLAnchorElement ... -> HTMLElement -> Element -> Node -> EventTarget -> Object
+ document实例 -> HTMLDocument「和 XMLDocument」 -> Document -> Node ...
+ window实例 -> Window -> WindowProperties -> EventTarget -> Object
+ 元素集合 -> HTMLCollection -> Object
+ 节点集合 -> NodeList -> Object
......
JS中的自定义类:自己创建的类「“类”有一个专业的名字“构造函数”」
*/
/* const Fn = function Fn(x, y) {
let sum = 10;
this.total = x + y;
this.say = function () {
console.log(`我计算的和是:${this.total}`);
};
}; */
/* let res1 = Fn(10, 20); //普通函数执行
console.log(res1); //undefined
console.log(window); //window.total、window.say */
/* let f1 = new Fn(10, 20); //构造函数执行「面向对象思维处理:创造类和实例 基于NEW执行,Fn是类,f1是实例」
console.log(f1);
let f2 = new Fn(100, 200);
console.log(f2);
console.log(f1.say === f2.say); //false 构造函数体中 this.xxx=xxx 给实例设置的私有属性方法 */
/* // Fn() //普通函数执行
// Fn; //代表函数Fn的堆内存地址 没执行
let f1 = new Fn(10, 20); //把构造函数执行,可以传递实参 有参数列表NEW 优先级:20
let f2 = new Fn; //不加小括号也可以执行的,只不过不允许传递实参 优先级:19
console.log(f1, f2); */
/* function Fn() {
this.x = 100;
this.y = 200;
this.getX = function () {
console.log(this.x);
}
}
Fn.prototype.getX = function () {
console.log(this.x);
};
Fn.prototype.getY = function () {
console.log(this.y);
};
let f1 = new Fn;
let f2 = new Fn;
console.log(f1.getX === f2.getX);
console.log(f1.getY === f2.getY);
console.log(f1.__proto__.getY === Fn.prototype.getY);
console.log(f1.__proto__.getX === f2.getX);
console.log(f1.getX === Fn.prototype.getX);
console.log(f1.constructor);
console.log(Fn.prototype.__proto__.constructor);
f1.getX();
f1.__proto__.getX();
f2.getY();
Fn.prototype.getY(); */
/* function C1(name) {
if (name) {
this.name = name;
}
}
function C2(name) {
this.name = name;
}
function C3(name) {
this.name = name || 'join';
}
C1.prototype.name = 'Tom';
C2.prototype.name = 'Tom';
C3.prototype.name = 'Tom';
alert((new C1().name) + (new C2().name) + (new C3().name));
// 'Tom' + undefined + 'join' => "Tomundefinedjoin" */
12、内置原型
/*
// 基于内置类原型扩展方法「实例可以直接调用」:设置的名字不要和内置的方法名冲突{一般自己会设置前缀 myXxx}
Array.prototype.unique = function unique() {
// this -> 我们要处理的数组
return Array.from(new Set(this));
};
let arr = [1, 3, 2, 4, 3, 2, 2, 3, 4, 2, 5, 6, 3, 2, 1, 4, 5];
arr = arr.unique().sort((a, b) => a - b);
console.log(arr);
// 调用所属类原型上的内置属性方法 实例.方法()
// + 方便
// + 实现链式写法「执行完一个方法,返回的结果还是当前类的实例,这样就可以继续调用,类为其提供的其它方法执行」
// arr.sort((a, b) => a - b).map(item => item * 10).filter(item => item < 50).push('zhufeng');
*/
/*
// 原始值类型的特殊性
// 10 是个数字,它本身是不具备键值对的,但是它是Number类的实例,可以调用Number.prototype上的方法
// (10).toFixed(2) 装箱的操作:浏览器首先会把原始值变为对象类型的实例「new Number(10) 或者 Object(10)」,然后再继续调用toFixed等方法...
// new Number(10) 是Number类的实例,它的结果是对象类型的,所以可以调用Number.prototype上的方法,但是它可以进行数学运算
// let n=new Number(10);
// n+10 拆箱的操作:把对象类型的实例变为原始值类型的值{Number(对象)} Symbol.toPrimitive -> valueOf -> toString -> 变为数字
*/
(function (proto) {
// 检测num是否是合法数字
const checkNum = num => {
num = +num;
return isNaN(num) ? 0 : num;
};
proto.plus = function plus(num) {
num = checkNum(num);
return this + num;
};
proto.minus = function minus(num) {
num = checkNum(num);
return this - num;
};
})(Number.prototype);
let n = 10;
let m = n.plus(10).minus(5);
console.log(m); //=>15(10+10-5)
13、检测当前属性是否为对象的 私有/公有属性
/* 检测当前属性是否为对象的 私有/公有属性 */
/* // 思路一:是它的属性,但是还不是私有的,这样只能是公有的属性
// BUG:如果这个属性即是私有的也是公有的,这种方式检测就不准确了
Object.prototype.hasPubProperty = function hasPubProperty(attr) {
// this -> 要检测的对象 attr -> 检测的属性
return (attr in this) && !this.hasOwnProperty(attr);
}; */
// 思路二:跳过私有的查找「无论私有有没有,都无所谓」,直接找公共的「所属类的原型 先找自己所属类的原型,没有基于__proto__再继续向上找,直到Object.prototype为止;如果中间找到返回true,找到头都没有就是false!」
Object.prototype.hasPubProperty = function hasPubProperty(attr) {
// this -> 要检测的对象 attr -> 检测的属性
let proto = Object.getPrototypeOf(this);
while (proto) {
if (proto.hasOwnProperty(attr)) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
};
let arr = [10, 20, 30];
arr.push = 'push';
console.log(arr.hasPubProperty('0')); //false
console.log(arr.hasPubProperty('push')); //true
console.log(arr.hasPubProperty('AAA')); //false
14、函数的多种角色
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();
15、函数的多种角色2
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function () {
console.log('wangwang');
}
Dog.prototype.sayName = function () {
console.log('my name is ' + this.name);
}
/* // Ctor:constructor缩写 想创在哪一个构造函数的实例
// params:实参集合 未来传递给构造函数的实参
function _new(Ctor, ...params) {
// @1 创造当前类的一个实例{空实例对象}
// 实例对象.__proto__ 指向 构造函数.prototype
let obj = {};
obj.__proto__ = Ctor.prototype;
// @2 像普通函数执行一样把构造函数执行,让函数中的THIS指向创建的实例对象
let result = Ctor.call(obj, ...params);
// @3 看函数的返回值,如果返回的是个对象,则以自己返回的为主,否则把创建的实例对象返回
if (result !== null && (typeof result === "object" || typeof result === "function")) return result;
return obj;
} */
/* 重写内置NEW */
const _new = function _new(Ctor, ...params) {
// 对Ctor的格式做校验:必须是个函数 & 必须具备prototype属性 & 不能是生成器函数 & 排除Symbol/BigInt ...
if (Ctor === Symbol || Ctor === BigInt || Object.prototype.toString.call(Ctor) !== "[object Function]" || !Ctor.prototype) throw new TypeError(`Ctor is not a constructor`);
// 核心处理
let obj,
result;
obj = Object.create(Ctor.prototype);
result = Ctor.call(obj, ...params);
if (result !== null && /^(object|function)$/.test(typeof result)) return result;
return obj;
};
let sanmao = _new(Dog, '三毛');
sanmao.bark(); //=>"wangwang"
sanmao.sayName(); //=>"my name is 三毛"
console.log(sanmao instanceof Dog); //=>true
16、this
/*
THIS:函数的执行主体「谁把它执行的,和函数在哪执行以及在哪定义都没有直接的关系」
全局上下文中的THIS是window/global
块级私有上下文没有自己的THIS,所用到的THIS都是继承其上级上下文中的「宿主环境中的」
所以平时我们研究的都是函数中的THIS指向
@1 DOM元素事件绑定:给当前元素的某个事件行为绑定方法,当事件行为触发,方法执行,方法中的THIS是当前DOM元素
document.body.onclick = function () {
// 点击执行:this -> body
};
document.body.addEventListener('click', function () {
// 点击执行:this -> body
});
@2 普通函数执行,看方法名前面是否有“点”;
+ 有“点”,那么它前面是谁THIS就是谁;
+ 没有“点”,THIS是window{非严格模式}/undefined{严格模式};
const fn = function fn() {console.log(this);};
let obj = {
name: 'zhufeng',
fn
};
fn(); //this->window
obj.fn(); //this->obj
arr.push(100):arr基于__proto__,找到Array.prototype.push方法,把push方法执行,实现数组新增;此时push中的this->arr;
arr.__proto__.push(100):直接找到Array.prototype.push这个方法并且执行,此时push中的this->arr.__proto__;
==>内置push方法实现了“方法中的THIS是谁,就把100加入到谁的末尾”
一般情况下,匿名函数「自执行函数 & 回调函数」执行,方法中的THIS一般是window/undefined,除了做过特殊的处理!!
(function(){
//this -> window/undefined
})();
arr.forEach(function(){
//this -> window/undefined
});
// forEach等数组迭代方法,传递第二个参数的目的:修改数组每一次迭代执行的回调函数中的THIS指向「forEach内部特殊的处理」
arr.forEach(function(){
//this -> obj
}, obj);
@3 构造函数执行「NEW执行」,构造函数体中的THIS指向当前类的实例
class Demo{
constructor(){
//this->d1
}
}
let d1 = new Demo;
@4 箭头函数中没有自己的THIS「类似于块级上下文」,在函数中用到的THIS是所处上下文中的;
let obj = {
name: 'zhufeng',
fn() {
// this->obj
let self = this;
setTimeout(function () {
console.log(this); //this->window
self.name = '珠峰培训';
}, 1000);
setTimeout(() => {
this.name = "第二次处理"; //obj.nme='...'
}, 1000);
}
};
obj.fn();
@5 我们可以基于call/apply/bind三个方法,简单粗暴的改变函数中的THIS指向
这三个方法都在Function.prototype上,每个函数都是Function类的实例,所以可以调用这些方法执行
fn.call(obj,10,20)
把fn执行,传递10/20两个实参,让函数中的THIS指向obj!!
详细描述:首先fn看自己的静态私有属性方法中是否有call等方法,如果有则执行这个方法;如果没有,则默认基于__proto__去找Function.prototype上的call方法;把call方法执行的时候:
+ call中的this->fn 函数中要修改的this->obj 传递10&20
+ call方法内部,会把fn「call中的this」执行,让函数中的this指向obj,传递10&20实参!!
fn.apply(obj,[10,20]):和call只有一个区别「从第二个参数开始,会把所有需要传递给函数的实参,以数组的形式进行处理」
----在三个及以上参数的情况下,call的性能要大于apply的性能
const fn=function(n,m){};
document.body.onclick=fn; //this->body n->ev m->undefined
// 基于call/apply改变THIS的时候,会同时需要执行的把函数立即执行「立即执行函数、立即改变THIS」;而bind不会立即改变this,也不会立即把函数执行,而是预先把传递的obj、10、29以及最后要执行的方法fn先存储起来!!
document.body.onclick=fn.bind(obj,10,20)
*/
/* Function.prototype.call = function call(context, ...params) {
// this->fn context->obj params->[10,20]
// 把fn执行,让fn中的this指向obj,并且把10/20传递给fn,接收其返回值然后再还回
// 如果context是原始值类型的值,则需要把其变为对象类型「原因:原始值设置属性属性是无效的」
if (context == null) context = window;
if (!/^(object|function)$/.test(typeof context)) context = Object(context);
let key = Symbol('KEY'), //新增的属性不能和context原始属性冲突
result;
context[key] = this; //让两者有关联
result = context[key](...params); //把方法执行,而且方法中的this就是context
delete context[key]; //把新增的属性移除掉
return result; //把函数执行的返回值返回
};
const fn = function fn(x, y) {
console.log(this, x, y);
return x + y;
};
let obj = {
name: 'zhufeng',
xxx: 100
};
// fn(); //this->window
// obj.fn(); //Uncaught TypeError: obj.fn is not a function
let res = fn.call(1000, 10, 20);
console.log(res); */
// =================
Function.prototype.bind = function bind(context, ...params) {
// this->fn context->obj params->[10,20]
let self = this;
// 执行bind返回一个代理函数,把代理函数绑定给元素的事件;事件触发,先执行代理函数,在代理函数中再把我们需要做的事情处理了!
return function proxy(...args) {
// args->[ev]
params = params.concat(args);
return self.call(context, ...params);
};
};
const fn = function fn(x, y, ev) {
console.log(this, x, y, ev);
};
let obj = {
name: 'zhufeng'
};
// document.onclick = fn; //this->document x->MouseEvent y->undefined ev->undefined
// document.onclick = fn.call(obj, 10, 20); //这样处理不行,call是立即执行函数,也就是还没等待点击,fn就执行了
document.onclick = fn.bind(obj, 10, 20); //this->obj x->10 y->20 ev->MouseEvent
/* document.onclick = function proxy(ev) {
// this->document ev->MouseEvent
// 核心目的是执行fn
fn.call(obj, 10, 20, ev);
}; */
17、检测数据类型
/*
JS中检测数据类型的办法
typeof [value]:检测数据类型的运算符
@1 返回结果是一个字符串,其次字符串中包含对应的数据类型,例如:“number”、“object”、“function”...
typeof typeof typeof [1,2,3] => "string"
@2 弊端
+ typeof null -> “object”
+ typeof 对象 -> 除函数对象被识别为“function”,其余的对象都是返回“object”
+ typeof 未被声明的变量 -> 不会抱错,而是“undefined”
除了这些以外,用typeof检测原始值类型「或者函数类型」还是非常的方便、准确的
@3 检测的原理
+ 所有的数据类型值在计算机底层都是按照二进制来进行存储的,而typeof检测类型,也是按照二进制值来进行检测的「特点:性能好」;所有以 000 开始的,都是对象类型,再排查一下有没有实现call,实现的是函数对象,没实现的就是其余的对象...而null这个值存储的二进制都是零,所以也被识别为“object”!!
instanceof:检测当前实例是否属于这个类「临时拉来做数据类型检测」
@1 实例 instanceof 构造函数,返回true/false
@2 可以做一些数据类型的检测「实现对typeof的补充,可以实现对象的细分」
@3 弊端
+ 无法基于instanceof检测是否为标准普通对象「纯粹对象:直接是Object类的实例」;因为所有对象都是Object类的实例,基于“xxx instanceof Object” 检测的时候,返回结果都是true;
+ 无法检测原始值类型的值;因为基于instanceof检测的时候,默认不会进行“装箱”操作!!
例如:10 instanceof Number -> false
+ 检测的结果不一定严谨;因为,原型链以及所属构造函数是谁,是可以用户自己去改变的!!
+ ...
@4 当我们基于 “[value] instanceof [Ctor]” 运算符进行数据类型的检测的时候
+ 首先调用 [Ctor][Symbol.hasInstance]([value]) 这个函数,如果存在这个函数,则直接基于这个函数处理即可
当代浏览器基本都有,因为Symbol.hasInstance在Function.prototype中,每一个构造函数都是Function的实例,都可以调用Function.prototype[Symbol.hasInstance]这个方法
+ 如果不存在这个函数,浏览器会按照当前[value]原型链一层层向上查找,直到找到Object.prototype为止;查看[Ctor].prototype是否出现在它的原型链中,如果出现了,则结果是true,说明[value]是[Ctor]的实例,反之则为false...
@5 分析instanceof的优缺点和底层实现机制,并且重写instanceof
constructor:获取当前实例所属的构造函数「临时」
@1 [value].constructor 获取其构造函数,验证是否为我们想检测的类
例如:[value].constructor===Array
@2 相比较于instanceof来讲
+ 可以检测原始值类型的值「排除null/undefined」
+ 检测是否为标准普通对象 [value].constructor===Object
+ 和instanceof一样,检测的结果仅供参考「constructor这个值是可以被肆意修改的」
Object.prototype.toString.call([value]):专门检测数据类型的办法
@1 调用Object.prototype.toString方法,让方法中的this指向检测的值,就是检测当前值的数据类型;
返回结果是一个字符串 “[object ?]”
例如:Object.prototype.toString.call(10) -> "[object Number]"
Object.prototype.toString.call(new Number(10)) -> "[object Number]"
它是所有检测数据类型的办法中,最强大、最稳定...的方式{除了写起来麻烦一些}
@2 返回结果是 “[object ?]”,“?” 会是啥呢?
+ 首先获取[value][Symbol.toStringTag]属性值,如果存在这个属性,则这个属性值是啥,“?”就是啥!
+ 如果没有这个属性,一般“?”是当前实例所属的构造函数!!
Symbol.prototype & BigInt.prototype & Math & GeneratorFunction.prototype & Promise.prototype & Set.prototype & Map.prototype ... 这些类的原型上,都有Symbol.toStringTag这个属性!!
Object.prototype.toString这个方法是用来检测数据类型的,而且方法内部规定:方法中的this是谁,我们就检测谁的类型,所以我们基于call方法去改变this指向!!!
-------
Array.isArray:检测是否为数组
isNaN:检测是否为有效数字
*/
// alert({
// name: 'xxx'
// }); //=>“[object Object]” alert会把编写的值转换为字符串宰输出,而对象toString的时候,调用的是Object.prototype.toString这个方法,而这个方法是检测数据类型的
// 真正转换标准普通对象为字符串
// + JSON.stringify 把对象变为 JSON 格式字符串 ->JSON.parse
// + Qs.stringify 依托Qs第三方库,我们把对象变为 urlencoded 格式字符串
// 前后端数据通信的时候,我们经常需要把对象变为指定的字符串格式,传递给服务器;或者把服务器返回的指定格式字符串,变为对象!!
/*
let obj = {
name: 'zhufeng',
age: 12,
teacher: 'zhou'
};
// console.log(JSON.stringify(obj)); //'{"name":"zhufeng","age":12,"teacher":"zhou"}'
console.log(Qs.stringify(obj)); //'name=zhufeng&age=12&teacher=zhou' */
//-------------Object.prototype.toString.call
// let obj = {},
// toString = obj.toString; //->Object.prototype.toString 基本上所有的数据类型,所属构造函数的原型上都有toString方法,一般都是用来转换为字符串的,只有Object.prototype.toString是用来检测数据类型的
// console.log(toString.call(10)); //"[object Number]"
// console.log(toString.call(new Number(10))); //"[object Number]"
// console.log(toString.call("zhufeng")); //"[object String]"
// console.log(toString.call(true)); //"[object Boolean]"
// console.log(toString.call(null)); //"[object Null]"
// console.log(toString.call(undefined)); //"[object Undefined]"
// console.log(toString.call(Symbol())); //"[object Symbol]"
// console.log(toString.call(10n)); //"[object BigInt]"
// console.log(toString.call({})); //"[object Object]"
// console.log(toString.call([])); //"[object Array]"
// console.log(toString.call(/^$/)); //"[object RegExp]"
// console.log(toString.call(function () {})); //"[object Function]"
// console.log(toString.call(new Date())); //"[object Date]"
// console.log(toString.call(new Error())); //"[object Error]"
// console.log(toString.call(Math)); //"[object Math]"
// console.log(toString.call(function* () {})); //"[object GeneratorFunction]"
// console.log(toString.call(Promise.resolve())); //"[object Promise]"
// 即使constructor值被修改 或者 基于Object.setPrototypeOf重定向实例的原型指向,结果也是不变的!!所以 toString.call 这种办法检测的结果是非常可靠的!!
/*
// const Fn = function Fn() {};
// let f = new Fn;
// console.log(toString.call(f)); //“[object Object]”
// 需求:期望自己写自定义构造函数,所创建出来的实例在检测数据类型的时候,可以返回的是“[object 自己的构造函数]”
const Fn = function Fn() {};
Fn.prototype[Symbol.toStringTag] = "Fn";
let f = new Fn;
console.log(toString.call(f)); //“[object Fn]”
*/
//--------------constructor
// let arr = [];
// let reg = /^$/;
// let num = 10;
// console.log(arr.constructor === Array); //true
// console.log(arr.constructor === Object); //false
// console.log(reg.constructor === Array); //false
// console.log(num.constructor === Number); //true
//--------------instanceof
// let obj = {};
// let arr = [];
// let reg = /^$/;
// let num = new Number(10);
// console.log(arr instanceof Array); //true
// console.log(obj instanceof Array); //false
// console.log(reg instanceof Array); //false
// console.log(typeof num); //"object"
// console.log(num instanceof Number); //true 说明num是Number类的一个实例「原始值对应的对象类型结果」
// console.log(arr instanceof Array); //true
// console.log(arr instanceof Object); //true
// console.log(reg instanceof Object); //true
// console.log(obj instanceof Object); //true
// console.log(num instanceof Object); //true
/* const Fn = function Fn() {};
Fn.prototype = Array.prototype;
let f = new Fn;
console.log(f); //从结构来看,f一定不是数组「数组的结构:数字索引、逐级递增、length属性...」
console.log(f instanceof Array); //true */
/* const Fn = function Fn() {
this.name = 'zhufeng';
};
Fn.prototype.sayName = function () {};
Fn.xxx = 'xxx';
Fn[Symbol.hasInstance] = function () { //这样设置是无效的
console.log(1);
return false;
};
Function.prototype[Symbol.hasInstance] = function () { //这样设置也是无效的
console.log(1);
return false;
};
let f = new Fn;
console.log(f instanceof Fn); */
/* class Fn {
name = 'zhufeng';
sayName() {}
// 当做对象,设置静态私有的属性方法「这样设置是有用的,所以重构“构造函数”的Symbol.hasInstance,只支持ES6中class创建的类,ES5中创建的构造函数不支持这样重构」
static xxx = 'xxx';
static[Symbol.hasInstance](obj) {
console.log(obj);
return false;
}
}
let f = new Fn;
console.log(f instanceof Fn); //false */
// instance_of:检测value是否为Ctor的实例
// value:要检测的实例
// Ctor:要检测的构造函数
const instance_of = function instance_of(value, Ctor) {
// 保证Ctor的格式也是正确的
if (typeof Ctor !== "function") throw new TypeError('Right-hand side of instanceof is not callable');
if (!Ctor.prototype) throw new TypeError('Function has non-object prototype in instanceof check');
// 不支持原始值类型值的检测
if (value === null) return false;
if (!/^(object|function)$/.test(typeof value)) return false;
// 支持Symbol.hasInstance方法的直接按照这个处理
if (typeof Ctor[Symbol.hasInstance] === "function") return Ctor[Symbol.hasInstance](value);
// 不支持的则按照原型链一层层的查找即可 Object.getPrototypeOf(value):获取value所属构造函数的原型对象
let proto = Object.getPrototypeOf(value);
while (proto) {
// Ctor.prototype出现在了value的原型链上「value是Ctor的实例对象」:直接返回true & 结束查找
if (proto === Ctor.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
};
18、jquery
(function (global, factory) {
"use strict";
if (typeof module === "object" && typeof module.exports === "object") {
module.exports = global.document ?
factory(global, true) :
function (w) {
if (!w.document) {
throw new Error("jQuery requires a window with a document");
}
return factory(w);
};
} else {
factory(global);
}
})(typeof window !== "undefined" ? window : this, function (window, noGlobal) {
"use strict";
var arr = [];
var slice = arr.slice;
var push = arr.push;
var indexOf = arr.indexOf;
//-----
var jQuery = function (selector, context) {
return new jQuery.fn.init(selector, context);
};
jQuery.fn = jQuery.prototype = {
jquery: "3.6.0",
constructor: jQuery,
};
//-----
var rootjQuery = jQuery(document),
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
init = jQuery.fn.init = function (selector, context, root) {
var match, elem;
if (!selector) return this;
root = root || rootjQuery; //$(document)
if (typeof selector === "string") {
if (selector[0] === "<" &&
selector[selector.length - 1] === ">" &&
selector.length >= 3) {
match = [null, selector, null];
} else {
match = rquickExpr.exec(selector);
}
if (match && (match[1] || !context)) {
if (match[1]) {
context = context instanceof jQuery ? context[0] : context;
jQuery.merge(this, jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
));
if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
for (match in context) {
// Properties of context are called as methods if possible
if (isFunction(this[match])) {
this[match](context[match]);
// ...and otherwise set as attributes
} else {
this.attr(match, context[match]);
}
}
}
return this;
// HANDLE: $(#id)
} else {
elem = document.getElementById(match[2]);
if (elem) {
this[0] = elem;
this.length = 1;
}
return this;
}
} else if (!context || context.jquery) {
return (context || root).find(selector);
} else {
return this.constructor(context).find(selector);
}
} else if (selector.nodeType) {
this[0] = selector;
this.length = 1;
return this;
} else if (isFunction(selector)) {
return root.ready !== undefined ?
root.ready(selector) :
selector(jQuery);
}
return jQuery.makeArray(selector, this);
};
init.prototype = jQuery.fn;
// 创造数组或者类数组
jQuery.makeArray = function (arr, results) {
var ret = results || []; //当前案例:JQ实例对象
if (arr != null) {
if (isArrayLike(Object(arr))) {
jQuery.merge(ret,
typeof arr === "string" ? [arr] : arr
);
} else {
push.call(ret, arr);
}
}
return ret;
};
// 合并两个数组或者类数组「把第二个集合,合并在第一个集合中」
jQuery.merge = function (first, second) {
var len = +second.length,
j = 0,
i = first.length;
for (; j < len; j++) {
first[i++] = second[j];
}
first.length = i;
return first;
};
/* 暴露API */
if (typeof define === "function" && define.amd) {
define("jquery", [], function () {
return jQuery;
});
}
if (typeof noGlobal === "undefined") window.jQuery = window.$ = jQuery;
return jQuery;
});
/*
JQ选择器:$([selector]) 后者 jQuery([selector]) => 把私有jQuery方法当做普通函数执行
+ new jQuery.fn.init/init(selector, context) 创在了init类的实例“@A”
+ @A.__proto__===init.prototype 而 init.prototype===jQuery.prototype
+ @A.__proto__===jQuery.prototype
+ 说明JQ选择器获取的结果是jQuery类的实例对象
面试题:把一个方法当做普通函数执行,能否获取到当前这个构造函数的实例对象吗?
JQ对象:JQ的实例对象「可以调用jQuery.fn上的属性和方法,但是浏览器内置方法无法调取」
它是一个类数组集合,集合中每一项存储的就是DOM元素对象
DOM元素对象:基于JS内置属性和方法,获取的DOM对象「不是JQ实例,不能调用jQuery.fn上的属性和方法,但是可以调用浏览器内置方法」
[selector]的各种情况:
+ 传递的是“假「false/0/NaN/""/null/undefined」”:返回空的
+ 传递的是字符串:
新创建的HTML字符串:按照HTML字符串动态创建对应的DOM的元素,最后插入到页面在
样式选择器字符串,就是为了获取指定DOM元素
+ 传递的是DOM元素对象:把DOM元素对象,变为JQ实例对象
+ 传递的是个函数:$(function(){}) <=> $(document).ready(function(){}) 监听DOMContentLoaded事件「而且是基于DOM2事件绑定监听的」,所以:它会等待页面结构一加载完(DOM TREE生成完),触发回调函数执行!!而且同一个页面可以使用多次!!
+ 传递的是其它值
数组或者类数组:把传递进来的数组和类数组和JQ实例对象合并,最后返回的还是JQ实例对象
不是数组或者类数组:把传递进来的值,直接添加到JQ实例的末尾
不管传递啥,最后返回的都是JQ实例对象
*/
19、异步任务分析一
/*
console.log(1);
while (1) {} //循环操作是同步编程:如果设置了死循环,当前循环这个事情永远结束不了,后期的所有其他任务也都无法执行「避免出现死循环」
console.log(2);
*/
/*
setTimeout(() => {
console.log(1);
}, 20);
console.log(2);
setTimeout(() => {
console.log(3);
}, 10);
console.log(4);
for (let i = 0; i < 90000000; i++) {
// do soming 预估需要90ms循环才可以结束
}
console.log(5);
setTimeout(() => {
console.log(6);
}, 8);
console.log(7);
setTimeout(() => {
console.log(8);
}, 15);
console.log(9);
*/
//==============
/*
实例:
[[PromiseState]]:状态 pending/fulfilled/rejected
[[PromiseResult]]:值 undefined
new Promise的时候会立即执行executor函数
实例.then(onfulfilled,onrejected)
@1 首先观察实例的状态,如果此时已经知道实例是成功或者失败的状态
+ 创建一个异步“微任务”「放在WebAPI中去监听,监听的时候知道它的状态,所以直接把执行的哪个方法,挪至到EventQueue中排队等着」
+ 状态是成功,后期执行的是onfulfilled
+ 状态是失败,后期执行的是onrejected
@2 如果实例此时的状态还是pending
+ 把onfulfilled/onrejected先存储到指定的容器中 「放在WebAPI中监听状态的改变」
+ 当后期执行resolve/reject的时候
+ 立即修改实例的状态和值「同步」
+ 创建一个异步微任务,后面让指定容器中的方法执行 「挪至到EventQueue中排队等着」
new Promise产生的实例,状态是成功还是失败,由 executor执行是否报错{执行报错,则实例是失败态}、以及resolve还是reject执行决定
Promise.resolve(100):直接创建状态是成功的实例
Promise.reject(100):直接创建状态是失败的实例
---
“实例.then”会返回一个全新的promise实例「p2」,这个实例的成功失败,由p1管控的onfulfilled或者onrejected不论哪个方法执行决定
+ 方法执行是否返回新的promise实例,如果没有返回:
+ 方法执行只要不报错,则p2就是成功的,值就是函数返回值
+ 执行报错,则p2是失败的,值是报错原因
+ 如果返回的是新的promise实例「new-promise」,则new-promise的状态和值决定了p2的状态和值
*/
/* let p1 = Promise.resolve(100);
let p2 = p1.then(value => {
console.log('成功:', value);
return Promise.reject(value * 10);
}, reason => {
console.log('失败:', reason);
return reason / 10;
});
// 因为p1管理的onfulfilled方法还在EventQueue中排队等待执行,所以此时p2的状态还是pending
// + 把onfulfilled/onrejected存储起来,放在WebAPI监听,监听p2状态的改变「把p1管控的onfulfilled执行就知道了」
// + 当p1对应的onfulfilled执行后,把p2变为失败,此时把p2对应的onrejected挪至到EventQueue中排队等着执行...
p2.then(value => {
console.log('成功:', value);
}, reason => {
console.log('失败:', reason);
}); */
/* console.log(1);
let p1 = new Promise(resolve => {
console.log(2);
setTimeout(() => {
console.log(3);
resolve(100);
console.log(4);
}, 1000);
console.log(5);
});
console.log(6);
p1.then(value => {
console.log('成功:', value);
}, reason => {
console.log('失败:', reason);
});
console.log(7);
// 答案:1 2 5 6 7 ...过1S后... 3 4 成功:100 */
/*
console.log(1);
let p1 = new Promise(function executor(resolve, reject) {
console.log(2);
resolve(100); //立即修改实例的状态和值
// reject(0);
console.log(3);
});
console.log(4);
p1.then(function onfulfilled(value) {
console.log('成功:', value);
}, function onrejected(reason) {
console.log('失败:', reason);
});
console.log(5);
// 答案:1 2 3 4 5 成功:100
*/
//============
/*
async/await:是promise+generator的语法糖
+ async修饰的函数,默认返回值会变为一个promise实例
+ 必须在函数中使用await,而且当前函数需要经过async修饰
*/
/* let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(100);
}, 1000);
}); */
/* p1.then(value => {
console.log('成功:', value);
}).catch(reason => { //.catch === .then(null,onrejected)
console.log('失败:', reason);
}); */
/* (async function () {
// await 后面放置的是promise实例「如果不是,则默认变为状态是成功,值是这个值的promise实例」
// + 把当前上下文中,await“下面的代码”作为一个“异步微任务”,放在WebAPI中去监听,监听实例的状态
// + 如果状态是成功,把其“下面的代码执行”挪至到EventQueue中排队等着
// + 如果状态是失败,则下面代码不会执行
let value = await p1;
console.log('成功:', value);
})(); */
/* (async function () {
// 捕获await后面实例是失败的?
try {
let value = await p1;
console.log('成功:', value);
} catch (err) {
console.log('失败:', err);
}
})(); */
//--------
// async function fn() {
// return 1;
// }
// console.log(fn()); //promise实例: 状态fulfilled 值1
// async function fn() {
// console.log(num);
// return 1;
// }
// console.log(fn()); //状态rejected 值是报错原因
// async function fn() {
// return Promise.reject('NO');
// }
// console.log(fn()); //返回值本身是promise实例,则以自己返回为主
20、异步任务分析二
console.log("script start");
setTimeout(() => {
console.log("setTimeout");
}, 1000);
Promise.resolve()
.then(function () {
console.log("promise1");
})
.then(function () {
console.log("promise2");
});
async function errorFunc() {
try {
await Promise.reject("error!!!");
} catch (e) {
console.log("error caught");
}
console.log("errorFunc");
return Promise.resolve("errorFunc success");
}
errorFunc().then((res) => console.log("errorFunc then res"));
console.log("script end");
21、promise
(function () {
"use strict";
// 检测是否为函数
var isFunction = function isFunction(obj) {
return typeof obj === "function" && typeof obj.nodeType !== "number" &&
typeof obj.item !== "function";
};
// 关于onfulfilled/onrejected执行返回值x的处理
var resolvePromise = function resolvePromise(promise, x, resolve, reject) {
if (promise === x) throw new TypeError('Chaining cycle detected for promise #<Promise>');
if (x !== null && /^(object|function)$/.test(typeof x)) {
var then;
try {
then = x.then;
} catch (err) {
reject(err);
}
if (typeof then === "function") {
// 是个promise实例
var called = false;
try {
then.call(
x,
function onfulfilled(y) {
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
function onrejected(r) {
if (called) return;
called = true;
reject(r);
}
);
} catch (err) {
if (called) return;
called = true;
reject(err);
}
return;
}
}
resolve(x);
};
// 验证是否为promise实例
var isPromise = function isPromise(x) {
if (x !== null && /^(object|function)$/.test(typeof x)) {
var then;
try {
then = x.then;
} catch (err) {
return false;
}
if (typeof then === "function") {
return true;
}
}
return false;
};
/* 核心部分 */
function Promise(executor) {
if (!isFunction(executor)) throw new TypeError("executor is not a function");
if (!(this instanceof Promise)) throw new TypeError("undefined is not a promise");
var self = this;
self.state = "pending";
self.result = undefined;
self.onfulfilledCallbacks = [];
self.onrejectedCallbacks = [];
var change = function change(state, result) {
if (self.state !== "pending") return;
self.state = state;
self.result = result;
setTimeout(function () {
var callbacks = state === "fulfilled" ? self.onfulfilledCallbacks : self.onrejectedCallbacks;
for (var i = 0; i < callbacks.length; i++) {
callbacks[i](self.result);
}
});
};
try {
executor(
function resolve(value) {
change("fulfilled", value)
},
function reject(reason) {
change("rejected", reason)
}
);
} catch (err) {
change("rejected", err);
}
};
/* 原型属性方法 */
Promise.prototype = {
constructor: Promise,
then: function then(onfulfilled, onrejected) {
// 如果onfulfilled/onrejected不传递,我们需要实现顺延(穿透)效果
if (typeof onfulfilled !== "function") {
onfulfilled = function onfulfilled(value) {
return value;
};
}
if (typeof onrejected !== "function") {
onrejected = function onrejected(reason) {
throw reason;
};
}
var self = this,
promise;
promise = new Promise(function (resolve, reject) {
switch (self.state) {
case "fulfilled":
setTimeout(function () {
try {
var x = onfulfilled(self.result);
resolvePromise(promise, x, resolve, reject);
} catch (err) {
reject(err);
}
});
break;
case "rejected":
setTimeout(function () {
try {
var x = onrejected(self.result);
resolvePromise(promise, x, resolve, reject);
} catch (err) {
reject(err);
}
});
break;
default:
self.onfulfilledCallbacks.push(function (value) {
try {
var x = onfulfilled(value);
resolvePromise(promise, x, resolve, reject);
} catch (err) {
reject(err);
}
});
self.onrejectedCallbacks.push(function (reason) {
try {
var x = onrejected(reason);
resolvePromise(promise, x, resolve, reject);
} catch (err) {
reject(err);
}
});
}
});
return promise;
},
catch: function mycatch(onrejected) {
return this.then(null, onrejected);
}
};
/* 静态属性方法 */
Promise.resolve = function resolve(value) {
return new Promise(function (resolve) {
resolve(value);
});
};
Promise.reject = function reject(reason) {
return new Promise(function (_, reject) {
reject(reason);
});
};
Promise.all = function all(promises) {
if (!/^\[object Array\]$/.test(Object.prototype.toString.call(promises))) throw new TypeError('promises is not a array');
var n = 0,
values = [];
return new Promise(function (resolve, reject) {
for (var i = 0; i < promises.length; i++) {
(function (i) {
var promise = promises[i];
if (!isPromise(promise)) promise = Promise.resolve(promise);
promise.then(
function onfulfilled(value) {
n++;
values[i] = value;
if (n >= promises.length) resolve(values);
},
function onrejected(reason) {
reject(reason);
}
);
})(i);
}
});
};
/* 规范测试 */
Promise.deferred = function deferred() {
var result = {};
result.promise = new Promise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
};
/* 暴露API */
if (typeof module === "object" && typeof module.exports === "object") module.exports = Promise;
if (typeof window !== "undefined") window.Promise = Promise;
})();
22、全局变量以及局部变量
23、全局变量以及局部变量