js数据类型
基本类型:Number String Boolean null undefined Symbol Bigint
引用类型:Object Function
Number
NaN: not a number 不是一个有效数字,但是属于number类型的
NaN和NaN本身不相等,和其它值也都不等
- isNaN([value]):检测当前值是否不是效数字,不是有效数字返回true,反之是有效数字则返回false
- Object.is(NaN, NaN):true 「is方法内部做了特殊的处理」
Infinity:无限大 -Infinity:无限小
把其它数据类型转换为number类型
- 显式转换「 Number([value]) | parseInt/parseFloat([value]) 两者的底层处理规则是不一样的」
- 隐式转换 -> Number
- 数学运算
- 基于==比较的时候
- isNaN([value])
- …
console.log(typeof NaN); //->'number'
console.log(typeof Infinity); //->'number'
console.log(NaN === NaN); //->false
console.log(Object.is(NaN, NaN)); //->true
复制代码
String
把其它值转换为字符串
- 显式:String([value]) 或者 [value].toString() 「->延展出数据类型检测」
- 隐式:
- 加号除了数学运算,还会产生字符串拼接
let n = '10',
m = 10;
console.log(10 + n); // “+”有一边出现了字符串「前提:有两边」会变为字符串拼接 '1010'
console.log(+n); // “+”只有一边,把值转换为数字 10
console.log(++n); // “++”和上面一样也是转换为数字,但是还会自身累加一 11
// i=i+1 i+=1 这两个一样
// i++/++i 大部分情况和上面一样,但是如果i本身的值是字符串则不一致「上面会处理为字符串拼接,下面是数学累加」
// 如果“+”两边,有一边是对象,则也可能会成为字符串拼接
// + 10+{} 或者 10+{name:'zhufeng'} -> '10[object Object]'
// + 10+[10] -> "1010"
// 特殊:
// + 10+new Number(10) -> 20
// + {}+10 或者 {name:'xxx'}+10 -> 10 「原因:{...}没有参与运算,浏览器认为其是一个代码块,计算‘+10’」
// + ({}+10) -> '[object Object]10'
// + let x={}+10 -> '[object Object]10' 下面两种不论是包起来还是赋值,从语法分析上都要参与到计算中了
// + ...
底层机制:对象在做数学运算的时候
// + 检测对象的 Symbol.toPrimitive 这个属性值,如果有则基于这个值进行运算,如果没有
// + 检测对象的 valueOf() 这个值「原始值:基本类型值」,如果有则基于这个值进行运算,如果不是原始值
// + 获取对象的 toString() 把其变为字符串 -> 如果是‘+’处理,则看到字符串了,所以变为字符串拼接
// + 如果最后就是想变为数字,则再把字符串转换为数字即可
// let obj = {};
// console.log(10 + obj); //->'10[object Object]'
let obj = {
[Symbol.toPrimitive]: function (hint) {
// hint:记录浏览器识别出来的,你会把其转换为什么类型的 default/string/number ...
console.log(hint);
return 10;
}
};
console.log(10 + obj); //20 hint->default
console.log(10 + new Number(10)); //20 new Number(10).valueOf() 10
console.log(Number(obj)) //10 hint->number
console.log(String(obj)) //'10' hint->string
复制代码
Boolean
0 "" null undefined NaN ---> false
复制代码
null undefined
console.log(null == undefined) //true 两者和其他都不等
复制代码
Symbol() 创建唯一值
//给对象设置一个Symbol属性:唯一属性 「减少属性处理上的冲突」
//宏观管理一些唯一的标识的时候,也是用唯一值
// new Symbol(); //Uncaught TypeError: Symbol is not a constructor
// console.log(Symbol('AA') === Symbol('AA')); //->false
/* let obj = {
[Symbol()]: 100
};
console.log(obj[Symbol()]); //->undefined */
/* let x = Symbol();
let obj = {
[x]: 100
};
console.log(obj[x]); //->100 */
/*
// 很多JS的内置原理都是基于这些Symbol的属性来处理的
Symbol.toPrimitive
Symbol.hasInstance
Symbol.toStringTag
Symbol.iterator
...
*/
复制代码
BigInt() 大数
BigInt([num])
xxxn
//大型项目中,服务器返回给客户端的数据中可能出现大数「服务器数据库中可以基于longint存储数值,这个值可能会超过最大安全数字」
// Number.MAX_SAFE_INTEGER : 9007199254740991 最大安全数字
// Number.MIN_SAFE_INTEGER
// 超过安全数字进行运算,结果是不准确的
复制代码
四大数据类型转换规则
数据类型的检测
- typeof
- 检测出来的结果是字符串
- typeof null -> ‘object’ 不属于对象,而是因为二进制存储值以000开头
- 检测对象细分的类型,结果都是’object’
- instanceof
- constructor
- Object.prototype.toString.call([value])
/*
* 把其他数据类型转换为Number类型
* 1.特定需要转换为Number的
* + Number([val])
* + parseInt/parseFloat([val])
* 2.隐式转换(浏览器内部默认要先转换为Number在进行计算的)
* + isNaN([val])
* + 数学运算(特殊情况:+在出现字符串的情况下不是数学运算,是字符串拼接)
* + 在==比较的时候,有些值需要转换为数字再进行比较
* + ...
* .......
*/
/*
* 把其它数据类型转换为字符串
* 1. 能使用的办法
* + toString()
* + String()
* 2. 隐式转换(一般都是调用其toString)
* + 加号运算的时候,如果某一边出现字符串,则是字符串拼接
* + 把对象转换为数字,需要先toString()转换为字符串,再去转换为数字
* + 基于alert/confirm/prompt/document.write...这些方式输出内容,都是把内容先转换为字符串,然后再输出的
* + ...
* ......
*/
/*
* 把其它数据类型转换为布尔
* 1. 基于以下方式可以把其它数据类型转换为布尔
* + ! 转换为布尔值后取反
* + !! 转换为布尔类型
* + Boolean([val])
* 2. 隐式转换
* + 在循环或者条件判断中,条件处理的结果就是布尔类型值
* + ...
*
* 规则:只有 ‘0、NaN、null、undefined、空字符串’ 五个值会变为布尔的FALSE,其余都是TRUE
*/
/*
* 在==比较的过程中,数据转换的规则:
* 【类型一样的几个特殊点】
* {}=={}:false 对象比较的是堆内存的地址
* []==[]:false
* NaN==NaN:false
* 【类型不一样的转换规则】
* 1. null==undefined:true,但是换成===结果是false(因为类型不一致),剩下null/undefined和其它任何数据类型值都不相等
* 2. 字符串==对象 要把对象转换为字符串
* 3. 剩下如果==两边数据类型不一致,都是需要转换为数字再进行比较
*/
复制代码
console.log([] == false) //true
// 对象 == 布尔 都转换为数字(隐式转换)
// 对象转换为数字:先toString转换为字符串(应该是先基于valueOf获取原始值,没有原始值再toString),再转换为数字
// [] -> '' -> 0 false -> 0 true -> 1
console.log(![] == false) // true
// ![] -> false
//把其他类型转换为字符串,一般都是直接用''包起来,只有{}普通对象调取的Object.prototype.toString,不是转换为字符串,而是检测数据类型,返回结果是'[object Object]'
//把其他类型转换为数字
// Number机制
console.log(Number('')) // 0
console.log(Number('10')) // 10
console.log(Number('10px')) // NaN 只要出现非有效数字字符结果都是NaN
console.log(Number(true)) // 1
console.log(Number(false)) // 0
console.log(Number(null)) // 0
console.log(Number(undefined)) // NaN
console.log(Number(Symbol(10))) // 报错
console.log(Number(BigInt(10))) // 10
//对象转化为数字,先valueOf,没有原始值再toString转换为字符串,最后把字符串转换为数字
// parseInt机制
// 从字符串第一位开始,查找有效数字字符(遇到非有效数字字符停止查找,不论后面是否还有数字字符),把找到的有效数字字符转换为数字,如果一个都没有找到返回NaN parseFloat比parseInt多识别一个小数点
parseInt('') // NaN
Number('') // 0
isNaN('') // false
parseInt(null) // NaN
Number(null) // 0
isNaN(null) // false
parseInt('12px') // 12
Number('12px') // NaN
isNaN('12px') // true
parseFloat('1.6px') + parseInt('1.2px') + typeof parseInt(null) // '2.6number'
isNaN(Number(!!Number(parseInt('0.8px')))) // false
typeof !parseInt(null) + !isNaN(null) // 'booleantrue'
10 + false + undefined + [] + 'Tencent' + null + true + {}
// 10 + false -> 10
// 10 + undefined -> NaN
// NaN + [] -> 'NaN'
// 'NaN' + 'Tencent' -> 'NaNTencent'
// 'NaNTencent' + null + true + {} -> 'NaNTencentnulltrue[object Object]'
10 + false + undefined + + 'Tencent' + null + true + {}
// 'NaN[object Object]'
// parseInt(value, [radix])
// radix是一个进制,不写或者0默认都是10(特殊情况:如果value是以0x开头,则默认值不是10是16)
// radix 2-36 不在结果是NaN
// 把value看作radix进制,转换为10机制
let arr = [10.18, 0, 10, 25, 23]
arr = arr.map(parseInt)
console.log(arr)
// parseInt('10.18', 0) // 10
// parseInt('0', 1) // NaN
// parseInt('10', 2) // 2
// parseInt('25', 3) // 2
// parseInt('23', 4) // 11
// parseInt(0xf) // 15
// parseInt(0xf, 16) // 21
复制代码
堆栈内存
堆栈内存
/* var a = 12;
var b = a;
b = 13;
console.log(a);
var a = {n: 12};
var b = a;
b['n'] = 13;
console.log(a.n); */
/* var a = {
n: 12
};
var b = a;
b = {
n: 13
};
console.log(a.n); */
var a = {
n: 1
};
var b = a;
a.x = a = {
n: 2
};
console.log(a.x);
console.log(b);
// 正常运算都是从右到左
/* a=b=xxx
b=xxx
a=xxx 或者 a=b */
// a.x 成员访问,运算优先级很高的
/* a.x=b=xxx
b=a.x=xxx
+ a.x=xxx
+ b=xxx */
复制代码
函数底层处理机制(创建+执行)
函数底层处理机制(闭包)
闭包
闭包:函数运行的一种机制(不是某种代码形式)
- 函数执行会形成一个新的私有上下文,如果上下文的某些内容(一般指的是堆内存地址)被上下文以外的一些事物(例如 变量/事件绑定等)所占用,则当前上下文不能被出栈释放(浏览器的垃圾回收机制GC所决定的) =>闭包的机制:形成一个不被释放的上下文
- 保护:保护私有上下文中的 私有变量 和外界互不影响
- 保存:上下文不被释放,那么上下文中的 私有变量 和 值 都会被保存起来,可以供其下级上下文中使用
- 弊端:如果大量使用闭包,会导致栈内存太大,页面渲染变慢,性能受到影响,所以真实项目中需要“合理使用闭包”;某些代码会导致栈溢出或者内存泄漏,这些操作都是需要我们注意的。
// 循环事件绑定
1. 闭包
2. let
3. 自定义属性
4。事件委托
复制代码
JS高阶编程技巧
利用闭包的机制,实现出来的一些高阶编程方式
- 模块化思想 (单例设计模式)
- 惰性函数
- 柯理化函数
- compose组合函数
- 高阶组价 -> React
- 函数防抖和节流
- bind
- …
// 模块化思想
// 单例->AMD(require.js)->CMD(sea.js)->CommonJS(Node)->ES6Module
//---------没有模块化思想之前,团队协作开发或者代码量较多的情况,回导致全局变量污染「全局变量冲突」
/*
// 实现天气版块
var time = '2020-11-01';
function queryData() {
// ...
}
function changeCity() {
//...
}
// 实现资讯版块
var time = '2020-10-31';
function changeCity() {
//...
}
*/
//-----------暂时基于闭包的“保护作用”防止了全局变量的污染 「但是因为每个版块的代码都是私有的,无法相互调用」
/* (function () {
var time = '2020-11-01';
function queryData() {}
function changeCity() {}
})();
(function () {
var time = '2020-10-31';
function changeCity() {}
})(); */
//----------基于某些方法去实现相互调用
/* (function () {
var time = '2020-11-01';
function queryData() {}
function changeCity() {}
// 把需要供别人调用的API方法,挂载到全局上「不能挂载太多,挂载多了,还是会引发全局变量的污染」
window.queryData=queryData;
})();
(function () {
var time = '2020-10-31';
function changeCity() {}
queryData();
})(); */
//---------
// 对象的特点:每一个对象都是一个单独的堆内存空间(单独的实例->Object),这样即使多个对象中的成员名字相同,也互不影响
// 仿照其他后台语言,其实obj1/obj2不仅仅称为对象名,更被称为“命名空间「给堆内存空间起一个名字」”
// -->每一个对象都是一个单独的实例,用来管理自己的私有信息,即使名字相同,也互不影响,其实这就是“JS中的单例设计模式”
/* var obj1 = {
name: 'zhufeng',
age: 12,
fn: function () {}
};
var obj2 = {
name: 'zhouxiaotian',
age: 30,
fn: function () {}
}; */
//----------高级单例设计模式:闭包+单例的结合,也是最早期的JS模块化思想
/* var weatherModule=(function () {
var time = '2020-11-01';
function queryData() {}
function changeCity() {}
return {
queryData:queryData,
changeCity:changeCity
};
})();
var infoModule=(function () {
var time = '2020-10-31';
function changeCity() {}
weatherModule.queryData();
return {
changeCity:changeCity
};
})();
var skinModule=(function(){
// ...
return {};
})(); */
复制代码
// 惰性函数
// window.getComputedStyle(元素):获取当前元素经过浏览器计算的样式「返回样式对象」
// 在IE6~8中,不兼容这种写法,需要使用 “元素.currentStyle” 来获取
// “属性 in 对象” 检测当前对象是否有这个属性,有返回true,反之false
/* function getCss(element, attr) {
if ('getComputedStyle' in window) {
return window.getComputedStyle(element)[attr];
}
return element.currentStyle[attr];
}
var body = document.body;
console.log(getCss(body, 'height'));
console.log(getCss(body, 'margin'));
console.log(getCss(body, 'background')); */
//-------优化思想:第一次执行getCss我们已经知晓是否兼容了,第二次及以后再次执行getCss,则不想在处理兼容的校验了,其实这种思想就是“惰性思想”「懒,干一次可以搞定的,绝对不去做第二次」
/* var flag = 'getComputedStyle' in window;
function getCss(element, attr) {
if (flag) {
return window.getComputedStyle(element)[attr];
}
return element.currentStyle[attr];
} */
/* function getCss(element, attr) {
// 第一次执行,根据是否兼容,实现函数的重构
if ('getComputedStyle' in window) {
getCss = function getCss(element, attr) {
return window.getComputedStyle(element)[attr];
};
} else {
getCss = function getCss(element, attr) {
return element.currentStyle[attr];
};
}
// 为了保证第一次也可以获取信息,则需要把重构的函数执行一次
return getCss(element, attr);
}
var body = document.body;
console.log(getCss(body, 'height'));
console.log(getCss(body, 'margin'));
console.log(getCss(body, 'background')); */
复制代码
// 柯里化函数
// 预先处理的思想「形成一个不被释放的闭包,把一些信息存储起来,以后基于作用域链,访问到事先存储的信息,然后进行相关的处理,所有符合这种模式(或者闭包应用的)都被称为柯理化函数」
// x:预先存储的值
/* function curring(x) {
// x->10
return function (...args) {
// args->[20]/[20,30]
args.unshift(x);
// 数组求和
var total = eval(args.join('+'));
return total;
};
} */
/* function curring(x) {
return function (...args) {
args.unshift(x);
return args.reduce((result, item) => result + item);
};
}
var sum = curring(10);
console.log(sum(20)); //10+20
console.log(sum(20, 30)); //10+20+30
*/
/*
// 数组的reduce方法:在遍历数组的过程中,可以累积上一次处理的结果,基于上次处理的结果继续遍历处理
// + 数组.reduce([callback],[initialValue])
var arr = [10, 20, 30, 40];
/!* var res = arr.reduce(function (result, item, index) {
// [initialValue]初始值不传递,result默认初始值是数组第一项,然后reduce从数组第二项开始遍历
// 每遍历数组中的一项,回调函数被触发执行一次
// + result 存储的是上一次回调函数返回的结果(除了第一次是初始值或者数组第一项)
// + item 当前遍历这一项
// + index 当前遍历这一项的索引
console.log(result);
return item + result;
});
console.log(res); *!/
var res = arr.reduce((result, item) => {
// 如果传递初始值,则result第一次的结果就是初始值,item从数组第一项开始遍历
return result + item;
}, 0);
*/
/*
Array.prototype.reduce = function reduce(callback, initial) {
var self = this, // this->arr
i = 0;
if (typeof callback !== "function") throw new TypeError('callback must be an function!');
if (typeof initial === "undefined") {
initial = self[0];
i = 1;
}
// 迭代数组每一项
for (; i < self.length; i++) {
var item = self[i],
index = i;
initial = callback(initial, item, index);
}
return initial;
};
var arr = [10, 20, 30, 40];
var total = arr.reduce((result, item) => {
return result + item;
});
console.log(total);
*/
复制代码
/*
在函数式编程当中有一个很重要的概念就是函数组合, 实际上就是把处理数据的函数像管道一样连接起来, 然后让数据穿过管道得到最终的结果。 例如:
const add1 = (x) => x + 1;
const mul3 = (x) => x * 3;
const div2 = (x) => x / 2;
div2(mul3(add1(0))); //=>3
而这样的写法可读性明显太差了,我们可以构建一个compose函数,它接受任意多个函数作为参数(这些函数都只接受一个参数),然后compose返回的也是一个函数,达到以下的效果:
const operate = compose(div2, mul3, add1)
operate(0) //=>相当于div2(mul3(add1(0)))
operate(2) //=>相当于div2(mul3(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;
// funcs:存储的是最后需要执行的函数及其顺序(最后传递的函数优先执行)
// + 执行compose只是把最后要执行的函数及顺序事先存储起来,函数还没有执行「柯理化思想」
// + 返回一个operate处理函数,执行operate,并且传递初始值,才按照之前存储的函数及顺序依次执行函数
function compose(...funcs) {
return function operate(x) {
if (funcs.length === 0) return x;
if (funcs.length === 1) return typeof funcs[0] === "function" ? funcs[0](x) : x;
return funcs.reduceRight(function (result, item) {
if (typeof item !== "function") return result;
return item(result);
}, x);
};
}
var operate = compose(div2, mul3, add1);
var result = operate(0);
console.log(result);
/* var result = div2(mul3(add1(0)));
console.log(result); //->1.5 */
/*
// 思考题:研究这个是如何实现的
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
*/
复制代码
js中声明变量
- 传统:var function
- ES6:let const import(模块导入)
// let vs var
1.var存在变量提升,而let不存在
2.全局上下文中 var声明的变量也相当于给GO新增一个属性,并且一个值发生改变,另一个值也会跟着变化(映射机制),但是let声明的变量,就是全局变量,和GO没有任何关系
3.在同一上下文中,let不能重复声明,var可以
4. 暂时性死区(浏览器暂存bug)
5. let/const/function会产生块级私有上下文,而var不会
/*
* EC(G)
* 变量提升: var n;
*/
console.log(n) //undefined
console.log(m) //Uncaught ReferenceError: Cannot access 'm' before initialization
var n = 12
let m = 13
let n = 12
console.log(n) //12
console.log(window.n) //undefined
var n = 12
console.log(n) //12
console.log(window.n) //12
window.n = 13
console.log(n) //13
n = 13 // window.n = 13 没有基于任何关键词声明的,则相当于给window设置一个属性
console.log(n) //13 首先看是否为全局变量,如果不是,则再看是否为window的属性
console.log(m) //如果两者都不是,则报变量未定义 Uncaught ReferenceError: m is not defined
//在代码执行之前,浏览器会做很多事情:词法分析、变量提升
//'词法分析阶段',如果发现基于let/const重复声明变量操作,报语法错误
console.log('ok')
var n = 12
var n = 13
let n = 14
console.log(n) //Uncaught ReferenceError: n is not defined
console.log(typeof n) //typeof检测一个未被声明的变量,不会报错 undefined
console.log(typeof n) //Uncaught ReferenceError: Cannot access 'n' before initialization
let n = 12
/*
* 上下文 & 作用域
* 全局上下文
* 函数执行形成的 私有上下文
* 块级上下文(除了对象/函数...的大括号,例如判断体if、循环体for switch while、代码块{})都可能会产生块级上下文
*/
{
var n = 12
console.log(n) //12
let m = 13
console.log(m) //13
}
console.log(n) //12
console.log(m) //Uncaught ReferenceError: m is not defined
for(var i=0;i<5;i++){
console.log(i) // 0 1 2 3 4
}
console.log(i) // 5
for(let i=0;i<5;i++){
console.log(i) // 0 1 2 3 4
}
console.log(i) // Uncaught ReferenceError: i is not defined
let i = 0
for(;i<5;i++){
console.log(i) // 0 1 2 3 4
}
console.log(i) // 5
// 浏览器帮助我们形成的闭包
for(let i=0;i<buttons.length;i++){
// 父级块级上下文 控制循环
// 第一轮循环 私有的块级上下文EC(B1) i=0
// 当前上下文中创建的一个小函数,被全局的按钮click占用了,EC(B1)不会被释放(闭包)
buttons[i].onclick = function(){
console.log(`当前点击按钮的索引:${i}`)
}
// ...
}
// 实现不了,产生不了闭包
let i = 0
for(;i<buttons.length;i++){
buttons[i].onclick = function(){
console.log(`当前点击按钮的索引:${i}`)
}
}
复制代码
变量提升机制
变量提升:在当前上下文中(全局/私有/块级),js代码自上而下执行之前,浏览器会提前处理一些事情(可以理解为词法解析的一个环节,词法解析一定发生在代码执行之前)
- 会把所有带var 和 function 关键字的进行提前声明或定义
- var 提前声明
- function 提前声明加定义
/*
* 代码执行之前: 全局上下文的变量提升
* var a; 默认值是undefined
*/
console.log(a) // undefined
var a = 12 // 创建值12 不需要再声明a(变量提升阶段完成了,完成的事情不会重新处理) a = 12赋值
a = 13 // 全局变量 a = 13
console.log(a) // 13
/*
* EC(G)变量提升
*/
// window.a undefined
console.log(a) //Uncaught ReferenceError: a is not defined
a = 12
console.log(a)
/*
* EC(G)变量提升 ES6中let 和 const不会
*/
console.log(a) //Uncaught ReferenceError: Cannot access 'a' before initialization
let a = 12
a = 13
console.log(a)
/*
* 基于var 和 function 在 全局上下文中声明的变量(全局变量)会映射到GO(全局对象 window)上一份,作为他的属性,而且接下来一个修改,另外一个也会跟着修改
*/
/*
* a = 12 也会给window设置属性 let 和 const 不会
*/
/*
* EC(G)变量提升
* 不论条件是否成立,都要进行变量提升(细节点:条件中带function的在新版本浏览器中只会提前声明,不会定义了)
* [老版本]
* var a;
* func = 函数
* [新版本]
* var a;
* func;
*/
if(!('a' in window)){
var a = 1
}
console.log(a) //undefined
复制代码
总结:
- var 和 function 关键字的进行提前声明或定义
- var 和 function 在 全局上下文中声明的变量(全局变量)会映射到GO
- 条件中带function的在新版本浏览器中只会提前声明,不会定义了
- 浏览器很懒,在变量提升和代码执行阶段 干过的事情不会再干
fn() //5
function fn () {console.log(1)}
fn() //5
function fn () {console.log(2)}
fn() //5
var fn = function() {console.log(3)}
fn() //3
function fn () {console.log(4)}
fn() //3
function fn () {console.log(5)}
fn() //3
// 有说函数的优先级高于变量
// 有说先声明var 再声明function
复制代码
var foo = 1
function bar () {
if(!foo){
var foo = 10
}
console.log(foo) // 10
}
bar()
复制代码
闭包作用域习题
变量提升
/*
* EC(G)中的变量提升
* var a;
* var b;
* var c;
* fn = 0x000; [[scope]]:EC(G)
*/
console.log(a, b, c); //=>undefined * 3
var a = 12,
b = 13,
c = 14;
function fn(a) {
/*
* EC(FN)私有上下文
* 作用域链:<EC(FN),EC(G)>
* 形参赋值:a=10
* 变量提升:--
*/
console.log(a, b, c); //=>10 13 14
a = 100;//私有a赋值100
c = 200;//全局c赋值200
console.log(a, b, c); //=>100 13 200
}
b = fn(10);//全局b赋值undefined
console.log(a, b, c); //=>12 undefined 200
复制代码
/*
* EC(G)变量提升
* var i;
* A = 0x000; [[scope]]:EC(G)
* var y;
* B = 0x001; [[scope]]:EC(G)
*/
var i = 0;
function A() {
/*
* EC(A)私有上下文
* 作用域链:<EC(A),EC(G)>
* 形参赋值:--
* 变量提升:
* var i;
* x = 0x002; [[scope]]:EC(A)
*/
var i = 10;
function x() {
/*
* EC(X1)私有上下文
* 作用域链:<EC(X1),EC(A)>
* 形参赋值:--
* 变量提升:--
*/
/*
* EC(X2)私有上下文
* 作用域链:<EC(X2),EC(A)>
* 形参赋值:--
* 变量提升:--
*/
console.log(i);
}
return x; //return 0x002;
}
var y = A(); //y=0x002;
y(); //=>10
function B() {
/*
* EC(B)私有上下文
* 作用域链:<EC(B),EC(G)>
* 形参赋值:--
* 变量提升:
* var i;
*/
var i = 20;
y();//=>10
}
B();
复制代码
/*
* EC(G)变量提升
* var a;
* var obj;
* fn = 0x000; [[scope]]:EC(G)
*/
var a = 1;
var obj = {
name: "tom"
}; //obj=0x001
function fn() {
/*
* EC(FN)
* 作用域链:<EC(FN),EC(G)>
* 形参赋值:--
* 变量提升:var a2;
*/
var a2 = a; //私有a2=1
obj2 = obj;
//obj2不是私有的,向全局找,如果全局也没有
// 情况1:输出obj2 直接报错 Uncaught ReferenceError: obj2 is not defined
// 情况2:赋值操作 相当于给window设置一个obj2的属性,并且赋值 window.obj2=0x001
a2 = a; //私有a2=1
obj2.name = "jack"; //遇到obj2,但是发现全局没有这个变量,它会继续再看window下是否有这个属性,如果window下有这个属性,则获取属性值,如果window下也没有这个属性,则报错(未定义变量) ->把0x001中的name属性值改为'jack'
}
fn();
console.log(a);//=>1
console.log(obj);//=>{name:'jack'}
/*
// VO(G):全局变量对象「全局上下文中声明的变量」
// GO「window」:全局对象 「浏览器默认开辟的一个堆内存,存储供JS调用的各种API的」
console.log(a);//首先看VO(G)中是否有,如果没有,则再看GO中有没有,如果也没有,则报错:a is not defined
*/
/*
// 在全局上下文中
var n = 10;
//老版本机制:VO(G)中声明一个n的全局变量,赋值10;特殊:“全局上下文”中,“基于var/function声明”的变量,也相当于给GO新增私有属性;并且之间存在映射关系「一个修改,另外一个也跟着修改」;
//新版本机制:“全局上下文”中,“基于var/function声明”的变量,直接都当做GO的属性存储起来;
console.log(n); //=>10
console.log(window.n); //=>10
m = 10; //给GO设置一个属性m -> window.m=10
console.log(m); //=>10
console.log(window.m); //=>10
*/
/*
// 基于let/const声明的变量,只是给VO(G)中设置全局变量,和GO没有任何的关系;
let n = 10;
console.log(n); //=>10
console.log(window.n); //=>undefined
*/
复制代码
/*
* EC(G)变量提升
* var a;
* fn=0x000; [[scope]]:EC(G)
*/
var a = 1;
function fn(a) {
/*
* EC(FN)
* 作用域链:<EC(FN),EC(G)>
* 形参赋值:a=1
* 变量提升:
* var a; 「没用了:因为此时私有上下文中已经有a了,不会重复声明」
* a=0x001; [[scope]]:EC(FN) 「不会重复声明,但是需要重新赋值」
*/
console.log(a); //=>函数
var a = 2; //私有a重新赋值为2
function a() {}
console.log(a); //=>2
}
fn(a); //fn(1)
console.log(a); //=>1
复制代码
/*
* EC(G)变量提升
* var a;
* fn=0x000; [[scope]]:EC(G)
*/
console.log(a); //=>undefined
var a = 12;
function fn() {
/*
* EC(FN)
* 作用域链:<EC(FN),EC(G)>
* 形参赋值:--
* 变量提升:var a;
*/
console.log(a); //=>undefined
var a = 13;
}
fn();
console.log(a); //=>12
复制代码
/*
* EC(G)变量提升
* var a;
* fn=0x000; [[scope]]:EC(G)
*/
console.log(a); //=>undefined
var a=12;
function fn(){
/*
* EC(FN)
* 作用域链:<EC(FN),EC(G)>
* 形参赋值:--
* 变量提升:--
*/
console.log(a); //=>12
a=13;
}
fn();
console.log(a); //=>13
复制代码
/*
* EC(G)变量提升
* fn=0x000; [[scope]]:EC(G)
*/
console.log(a); //先看VO(G)中是否有,没有再看GO中是否有,也没有 Uncaught ReferenceError: a is not defined
a=12;
function fn(){
console.log(a);
a=13;
}
fn();
console.log(a);
复制代码
/*
* EC(G)变量提升
* var foo;
*/
var foo = 'hello';
(function (foo) {
/*
* EC(AN)
* 作用域链:<EC(AN),EC(G)>
* 形参赋值:foo='hello'
* 变量提升:var foo;
*/
console.log(foo); //=>'hello'
var foo = foo || 'world';
// A||B A如果为假返回B的值,A为真返回A的值
// A&&B A如果为假返回A的值,A为真返回B的值
// 同时存在 &&优先级高于||
console.log(foo); //=>'hello'
})(foo); //自执行函数执行,把全局foo的值“hello”当做实参传递进来
console.log(foo); //=>'hello'
复制代码
变态机制
console.log(foo); //undefined
{
console.log(foo); //函数
function foo() {}
foo = 1;
console.log(foo); //1
}
console.log(foo); //函数
复制代码
/*
* EC(G)中的变量提升
* function foo;
* function foo;
*/
console.log(foo); //=>undefined
{
/*
* EC(B)变量提升
* foo = 0x000;
* foo = 0x001;
*/
console.log(foo); //=>0x001
function foo() {0} //特殊:之前对foo的操作都给全局一份 全局foo=0x001
console.log(window.foo); //=>0x001
foo = 1;
function foo() {1} //特殊:之前对foo的操作都给全局一份 全局foo=1
console.log(foo); //=>1
}
console.log(foo); //=>1
复制代码
{
function foo() {}
foo = 1;
function foo() {} //特殊:之前对foo的操作都给全局一份
foo = 2;
}
console.log(foo); //=>1
复制代码
var x = 1;
function func(x, y = function anonymous1(){x = 2}){
x = 3;
y();
console.log(x); //2
};
func(5);
console.log(x); //1
复制代码
var x = 1;
function func(x, y = function anonymous1(){x = 2}){
var x = 3;
y();
console.log(x); //3
};
func(5);
console.log(x); //1
复制代码
var x = 1;
function func(x, y = function anonymous1(){x=2}){
var x = 3;
var y = function anonymous2(){x = 4}
y();
console.log(x); //4
};
func(5);
console.log(x); //1
复制代码
/*
* arr.map((item,index)=>{
* return xxx;
* }) ->数组迭代的方法
*
* parseInt(xxx):把xxx转换为字符串,紧接着从字符串左侧第一个字符开始查找,找到有效数字字符「直到遇到一个非有效数字字符结束(不论后面是否还有,都不在查找)」,把找到的有效数字字符转换为数字!
* parseInt(xxx,radix):radix进制,在xxx字符串中,从左到右查找符合radix进制的信息,把找到的结果最后转换为10进制
* radix:取值范围2~36之间,不在这个之间,则最后结果都是NaN
* radix:没有设置或者设置为0,则默认都是按照10进制处理的「特殊:如果字符串是按照0x开始的,则默认值不是10,而是16」
*/
/* let arr = [27.2, 0, '0013', '14px', 123];
arr = arr.map(parseInt);
// parseInt(27.2) -> parseInt('27.2') -> 27
// parseInt(0,1) -> NaN
// parseInt('0013',2) -> '001' 把找到的2进制转换为10进制
// 1*2^0 + 0*2^1 + 0*2^2 =>1 + 0 + 0 =>1
// parseInt('14px',3) -> '1' 把找到的3进制转换为10进制
// 1*3^0 =>1
// parseInt(123,4) -> parseInt('123',4) -> '123' 把找到的4进制转换为10进制
// 3*4^0 + 2*4^1 + 1*4^2 =>3+8+16 =>27
console.log(arr);
*/
parseInt(070)
// JS中遇到“以0开始的数字”,浏览器解析阶段 就会把其当做8进制,最后转换为十进制
// 0*8^0 + 7*8^1 + 0*8^2 =>56
// parseInt(56) -> parseInt('56',10) */
复制代码
var a = 4
function b(x, y, a) {
console.log(a) //3
arguments[2] = 10
console.log(a) //10
}
a = b(1, 2, 3)
console.log(a) //undefined
复制代码
function fn(x, y, z) {
// 初始ARGUMENTS : {0:1,1:2,length:2}
// 形参赋值:x=1 y=2 z=undefined
// 「映射机制 ARG[0]:x ARG[1]:y 」
arguments[0]=10;
console.log(x); //10
y=20;
console.log(arguments[1]); //20
// 最开始没有建立起来映射机制,后期不论如何处理,都没有关系
console.log(z); //undefined
arguments[2]=30;
console.log(z); //undefined
}
fn(1, 2);
复制代码
// 把自执行函数执行的返回结果赋值给test
// test=0x001;
var test = (function (i) {
/*
* EC(AN) 「闭包」
* 作用域链:<EC(AN),EC(G)>
* 形参赋值:i=2
* 变量提升:--
*/
return function () {
/*
* EC(TEST)
* 作用域链:<EC(TEST),EC(AN)>
* 初始ARGUMENTS:{0:5,length:1}
* 形参赋值:--
* 变量提升:--
*/
alert(i *= 2); //=>让EC(AN)中的i在自身基础上累乘2 =>"4"
}; //return 0x001; [[scope]]:EC(AN)
})(2);
test(5);
复制代码
// func执行传递的值没啥卵用
var x = 4;//3 2 1
function func() {
return function (y) { //0x001 0x002
console.log(y + (--x)); //x是全局的
};
}
var f = func(5); //f=0x001
f(6); //=>9
func(7)(8); //0x002(8) =>10
f(9); //=>10
console.log(x); //=>1
复制代码
var x = 5,
y = 6;
function func() {
/*
* EC(F1)
* 作用域链:<EC(F1),EC(G)>
* 形参赋值:--
* 变量提升:--
*/
x += y; //改变全局的x=11
func = function (y) { //全局的func=0x001 [[scope]]:EC(F1) 「函数重构」
/*
* EC(F2)
* 作用域链:<EC(F2),EC(F1)>
* 形参赋值:y=3
* 变量提升:--
*/
console.log(y + (--x)); //先把全局的x=10 =>13
};
console.log(x, y); //=>11 6
}
func(4);
func(3);
console.log(x, y); //=>10 6
复制代码
//===============作业
// 简述你对闭包的理解,以及其优缺点?「剧本」
// + 基本介绍:ECStack、EC、VO、AO、GO、SCOPE、SCOPE-CHAIN、GC
// + 优 缺 点:保存和保护 、性能消耗(内存泄漏)
// + 实战应用:
// + 项目实战应用:循环事件绑定「突出:事件委托」、LET和VAR
// + 插件组件封装:JS高阶编程技巧「单例设计模式、惰性函数、柯理化函数、compose组合函数」
// + 源码阅读应用:Lodash源码「函数的防抖和节流」、JQ的源码、redux、react-redux「高阶组件」...
// + ...
// + 自己的思想和理解「一句话概括」
// 总结个5min左右的话术,回答过程中拒绝“背书式”回答,通俗一些,安排一些场景...
// 小技巧:你也可以写一篇文章,详细的介绍闭包「面试的时候,给面试官看,它边看你边简单说一些」
//===============作业
// 简述let和var的区别?
// + 变量提升
// + 重复声明
// + 全局下声明和GO的关系
// + 暂时性死区 「突出 “typeof检测一个没有被声明变量,结果是undefined” 的作用 -> 封装插件组件」
// + 块级作用域
// + ...
复制代码
// 匿名函数“具名化”:这样的写法是符合规范的
// const func = function func() {};
// document.body.onclick = function bodyclickhandle() {};
// (function anonymous(){})();
// ...
// + 匿名函数具名化,设置的名字不属于当前函数所在作用域中的变量
/* (function b() {})();
console.log(b); //Uncaught ReferenceError: b is not defined */
// + 函数名只能在函数内部使用:好处就是,后期匿名函数也可以实现递归调用「严格模式下禁止arguments.callee的使用」
/* "use strict";
(function b() {
console.log(b); //=>函数本身
console.log(arguments.callee); //Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
})(); */
// + 并且函数内部,直接修改它的值也是无效的
/* (function b() {
b = 10;
console.log(b); //=>函数
})(); */
// + 除非函数内部重新声明了这个变量,则可以修改这个名字的值:基于var/let/const/function都可以
/* (function b() {
let b = 10;
console.log(b); //=>10
})(); */
var b = 10;
(function b() {
/* b = 20;
console.log(b); //=>函数 */
let b = 20;
console.log(b); //=>20
})();
console.log(b); //=>10
/* var b = 10;
(function b(b) {
// 没有被声明过,b都是按照具名化的名字处理;但是不论基于何种方式一但被声明了,则按照变量处理,和函数名字没关系了;
console.log(b); //=>20
})(20);
console.log(b); */
复制代码
function fn(...outerArgs) {
return function anonymous(...innerArgs) {
return outerArgs.concat(innerArgs).reduce(function handle(result, item) {
return result + item;
}, 0);
};
}
const fn = (...outerArgs) => (...innerArgs) => outerArgs.concat(innerArgs).reduce((result, item) => result + item, 0);
let res = fn(1, 2)(3);
console.log(res);
复制代码
var num = 10; //window.num=10
var obj = {
num: 20
}; //obj=0x000;
obj.fn = (function (num) {
/*
* EC(AN) 「闭包」
* 作用域链:<EC(AN),EC(G)>
* 初始THIS:window
* 形参赋值:num=20 //->21 22 23
* 变量提升:--
*/
this.num = num * 3; //window.num=num*3=60
num++;
return function (n) {
/*
* EC(FN1) -> fn(5)
* 作用域链:<EC(FN1),EC(AN)>
* 初始THIS:window
* 形参赋值:n=5
* 变量提升:--
*/
/*
* EC(FN2) -> obj.fn(10)
* 作用域链:<EC(FN2),EC(AN)>
* 初始THIS:obj
* 形参赋值:n=10
* 变量提升:--
*/
this.num += n; //window.num=window.num+n=65 obj.num=obj.num+n=30
num++;
console.log(num); //=>22 23
}; //obj.fn=0x001; [[scope]]:EC(AN)
})(obj.num);
var fn = obj.fn; //fn=0x001;
fn(5);
obj.fn(10);
console.log(num, obj.num); //=>65 30
复制代码
// 函数中的THIS是谁,和函数在哪定义的以及在哪执行的都没有关系,按照总结的规律去分析执行主体即可
let obj = {
fn: (function () {
return function () {
console.log(this);
} //obj.fn=0x000;
})()
};
obj.fn(); //=>obj
let fn = obj.fn; //fn=0x000;
fn(); //=>window
复制代码
var fullName = 'language'; //window.fullName='language';
var obj = {
fullName: 'javascript',
prop: {
getFullName: function () {
return this.fullName;
}
}
};
console.log(obj.prop.getFullName());
//=>this:obj.prop =>obj.prop.fullName =>undefined
var test = obj.prop.getFullName;
console.log(test());
//=>this:window =>window.fullName =>'language'
复制代码
var name = 'window'; //window.name='window'
var Tom = {
name: "Tom",
show: function () {
// this->window
console.log(this.name); //=>'window'
},
wait: function () {
// this->Tom
var fun = this.show;
fun();
}
};
Tom.wait();
复制代码
window.val = 1;
var json = {
val: 10,
dbl: function () {
this.val *= 2;
}
}
json.dbl();
//->this:json ->json.val=json.val*2 ->json.val=20
var dbl = json.dbl;
dbl();
//->this:window ->window.val=window.val*2 ->window.val=2
json.dbl.call(window);
//->this:window ->window.val=window.val*2 ->window.val=4
alert(window.val + json.val); //=>"24"
复制代码
(function () {
var val = 1; //->2
var json = {
val: 10,
dbl: function () {
// this->json
val *= 2; //val=val*2 此处的val是变量 json.val是对象的属性
}
};
json.dbl();
alert(json.val + val); //=>"12"
})();
复制代码
curring
/* function add(...params) {
const proxy = (...args) => {
// 把每一次传递的信息都保存在原始的集合中
params = params.concat(args);
return proxy;
};
proxy.toString = () => {
// 基于函数运算或者输出的时候,隐式调用函数的toString,我们在这里实现求和即可
return params.reduce((result, item) => result + item);
};
return proxy;
} */
function curring() {
let params = [];
let add = function (...args) {
params = params.concat(args);
return add;
}
add.toString = function () {
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
/*
// 基于函数进行运算,或者输出的时候,一般都会调用函数的toString
function fn() {}
fn.toString = function () {
console.log('OK');
};
// console.log(fn + 1); //会依次调取fn的 Symbol.toPrimitive/valueOf/toString 这三个属性
console.log(fn);
*/
复制代码
/*
* 函数的防抖(debounce)和节流(throttle)
* 在“高频”触发的场景下,需要进行防抖和节流
* + 狂点一个按钮
* + 页面滚动
* + 输入模糊匹配
* + ...
* 我们自己设定,多长的时间内,触发两次及以上就算“高频”:封装方法的时候需要指定这个频率(可以设置默认值)
* 「防抖」在某一次高频触发下,我们只识别一次(可以控制开始触发,还是最后一次触发);详细:假设我们规定500MS触发多次算是高频,只要我们检测到是高频触发了,则在本次频繁操作下(哪怕你操作了10min)也是只触发一次...
* 「节流」在某一次高频触发下,我们不是只识别一次,按照我们设定的间隔时间(自己规定的频率),没到达这个频率都会触发一次;详细:假设我们规定频率是500MS,我们操作了10min,触发的次数=(10*60*1000)/500
*/
function debounce(func, wait, immediate) {
// 多个参数及传递默认的处理
if (typeof func !== "function") throw new TypeError("func must be an function!");
if (typeof wait === "undefined") wait = 500;
if (typeof wait === "boolean") {
immediate = wait;
wait = 500;
}
if (typeof immediate !== "boolean") immediate = false;
// 设定定时器返回值标识
let timer = null;
return function proxy(...params) {
let self = this,
now = immediate && !timer;
clearTimeout(timer);
timer = setTimeout(function () {
timer = null;
!immediate ? func.call(self, ...params) : null;
}, wait);
// 第一次触发就立即执行
now ? func.call(self, ...params) : null;
};
}
/*
function handle(ev) {
// 具体在点击的时候要处理的业务
console.log('OK', this, ev);
}
submit.onclick = debounce(handle, true);
// submit.onclick = proxy; 疯狂点击的情况下,proxy会被疯狂执行,我们需要在proxy中根据频率管控handle的执行次数
// submit.onclick = handle; //handle->this:submit 传递一个事件对象
*/
// 业务场景中处理的技巧1:标识判断 类似节流
/* let flag = false;
submit.onclick = function () {
if (flag) return;
flag = true;
console.log('OK');
setTimeout(() => {
// 事情处理完
flag = false;
}, 1000);
}; */
/* // 业务场景中处理的技巧2:按钮重置为灰色,移除事件绑定 类似节流
function handle() {
submit.onclick = null;
submit.disabled = true;
// ...
console.log('OK');
setTimeout(() => {
submit.onclick = handle;
submit.disabled = false;
}, 1000);
}
submit.onclick = handle; */
function throttle(func, wait) {
if (typeof func !== "function") throw new TypeError("func must be an function!");
if (typeof wait === "undefined") wait = 500;
let timer = null,
previous = 0; //记录上一次操作的时间
return function proxy(...params) {
let self = this,
now = new Date(), //当前这次触发操作的时间
remaining = wait - (now - previous);
if (remaining <= 0) {
// 两次间隔时间超过wait了,直接执行即可
clearTimeout(timer);
timer = null;
previous = now;
func.call(self, ...params);
} else if (!timer) {
// 两次触发的间隔时间没有超过wait,则设置定时器,让其等待remaining这么久之后执行一次「前提:没有设置过定时器」
timer = setTimeout(function () {
clearTimeout(timer);
timer = null;
previous = new Date();
func.call(self, ...params);
}, remaining);
}
};
}
function handle() {
console.log('OK');
}
window.onscroll = throttle(handle, 500);
// window.onscroll = proxy;
/* window.onscroll = function () {
// 默认情况下,页面滚动中:浏览器在最快的反应时间内(4~6MS),就会识别监听一次事件触发,把绑定的方法执行,这样导致方法执行的次数过多,造成不必要的资源浪费
console.log('OK');
}; */
复制代码
// 利用JS的暂时性死区「基于typeof检测一个未被声明的变量,结果是undefined」:如果是在浏览器或者webview环境下运行JS,则A=window,如果是在Node环境下执行,则A=global或者当前模块
var A = typeof window !== "undefined" ? window : this;
// 回调函数:把一个函数作为实参值,传递给另外一个函数执行,在执行过程中,把传递的函数执行
var B = function (window, noGlobal) {
// 浏览器环境下
// window=window
// noGlobal=undefined
"use strict";
var jQuery = function (selector, context) {
//...
};
var _jQuery = window.jQuery,
_$ = window.$;
jQuery.noConflict = function (deep) {
if (window.$ === jQuery) {
window.$ = _$;
}
if (deep && window.jQuery === jQuery) {
window.jQuery = _jQuery;
}
return jQuery;
};
// 支持AMD模块化思想 require.js
if (typeof define === "function" && define.amd) {
define("jquery", [], function () {
return jQuery;
});
}
// 浏览器环境下
if (typeof noGlobal === "undefined") {
// 把私有的方法暴露到全局对象上
window.jQuery = window.$ = jQuery;
}
};
// 自执行函数
(function (global, factory) {
"use strict";
if (typeof module === "object" && typeof module.exports === "object") {
// 当前运行JS的环境是支持CommonJS模块规范「node.js/webpack,浏览器默认不支持」
// ...
} else {
// 浏览器或者webview环境
// -> B(window)
factory(global);
}
})(A, B);
//============================
(function () {
function ModalPlugin() {}
// ...
// 防止冲突处理
var _M = window.M;
ModalPlugin.noConflict = function noConflict() {
if (window.M === ModalPlugin) {
window.M = _M;
}
return ModalPlugin;
};
// 暴露API:支持浏览器「直接SCRIPT导入」 && CommonJS「支持在webpack中或者ES6Module导入」
if (typeof window !== "undefined") {
window.M = window.ModalPlugin = ModalPlugin;
}
if (typeof module === "object" && typeof module.exports === "object") {
module.exports = ModalPlugin;
}
})();
复制代码
this的几种情况
- 事件绑定
// 不论是DOM0还是DOM2级事件绑定,给元素E的某个事件行为绑定方法,当事件触发方法执行,方法中的this是当前元素E本身
// 特殊情况:1.IE6~8中基于attachEvent实现DOM2事件绑定,事件触发方法执行,方法中的this不在是元素本身,大部分情况都是window 2.如果基于call/apply/bind强制改变了函数中的this,我们也是以强制改变的为主
document.body.onclick = function () {
console.log(this);
};
document.body.addEventListener('click', function () {
console.log(this);
});
复制代码
- 普通函数执行
// 函数执行,看函数前面是否有“点”,有“点”,“点”前面是谁this就是谁,没有“点”this是window「JS严格模式下是undefined」
// fn() -> this:window/undefined
// obj.fn() -> this:obj
// xxx.__proto__.fn() -> this:xxx.__proto__
// 自执行函数执行:其内的this一般都是window/undefined
// 回调函数中的this一般也是window/undefined,除非做过特殊的处理
// 括号表达中的this很变态
/* function fn() {
console.log(this);
}
var obj = {
name: 'zhufeng',
fn: fn
};
fn(); //this->window
obj.fn(); //this->obj
(obj.fn)(); //this->obj 小括号中只有一项,不算是括号表达式
(fn, 10, obj.fn)(); //this->window 小括号中有多项,只取最后一项,如果把其执行,不论之前this是谁,现在基本上都会变为window「括号表达式」
*/
// var obj = {
// num: (function () {
// // 把自执行函数执行的返回值,赋值给obj.num成员
// console.log(this); //->window
// return 10;
// })()
// };
// 回调函数:把一个函数作为值,传递给另外一个函数,在另外一个函数执行中,把传递进来的函数执行...
// var obj = {
// name: 'zhufeng'
// };
// setTimeout(function () {
// console.log(this); //->window
// }, 1000);
// [10].forEach(function () {
// console.log(this); //->window
// });
// [10].forEach(function () {
// console.log(this); //->obj
// }, obj);
复制代码
- 构造函数执行
- 箭头函数执行
// 箭头函数中没有自己的this,所用到的this都是所处上下文中的this
//在浏览器端运行JS代码,非函数中的this一般都是window;研究this都是研究函数中的this;有一个特殊的,就是ES6+中“块级上下文”中的this,是其所在上下文中的this「理解为:块级上下文是没有自己this的」
let obj = {
n: 0,
fn() {
// this->obj
/*
let self = this;
setTimeout(function () {
// console.log(this); //this->window
self.n++;
}, 1000);
*/
setTimeout(() => {
// this -> 上级上下文中的this -> obj
this.n++;
}, 1000);
}
};
obj.fn();
复制代码
- 基于call/apply/bind强制改变this
// Function.prototype : call/apply/bind 都是用来改变this指向的,以后函数执行调用这三个方法就可以实现this的改变
/*
Function.prototype.call = function call(context) {
// ...
};
*/
function fn(x, y, ev) {
console.log(this, x, y, ev);
}
let obj = {
name: 'zhufeng'
};
// obj.fn(); //Uncaught TypeError: obj.fn is not a function obj和fn没有关系,我们无法基于obj.fn来改变fn中的this
// ==BIND== 并不会把函数立即执行,它是预先处理函数中的THIS和参数的
// document.body.onclick = fn; //->把fn方法本身作为值绑定给BODY的点击事件,当触发BODY的点击操作,浏览器会帮助我们把fn函数执行「并且传递一个事件对象」,方法中的THIS->BODY =>this:body x:MouseEvent y:undefined
// document.body.onclick = fn(); //->立即把fn执行,把其执行的返回结果当作值,赋值给事件绑定,事件触发执行的是返回结果
// 需求:点击BODY的时候,把FN执行,并且让THIS->OBJ,而且传递10/20给X/Y
// document.body.onclick = fn.call(obj, 10, 20); //这样处理不行:因为call/apply都是把函数立即执行的,还没有等到点击的时候,函数都执行完了
/* document.body.onclick = function (ev) {
// 创建一个匿名函数,绑定给点击事件,触发BODY点击行为的时候,执行的是匿名函数
// + this->body
// + ev->传递的MouseEvent事件对象
// 在匿名函数中,我们自己执行fn即可,接下来想改啥改啥
fn.call(obj, 10, 20, ev);
}; */
// document.body.onclick = fn.bind(obj, 10, 20);
// ==CALL/APPLY== 都是立即执行函数,并且改变函数中的THIS,再并且给函数传递参数信息
/*
fn.call(obj, 10, 20);
// 1.fn实例基于__proto__找到Function.prototype上的call方法,把call方法执行
// + call方法中的this:fn
// + context存储的是传递的obj
// 2.call方法内部做的事情
// + 把fn执行(也就是把call中的this执行)
// + 把fn中的this改变为第一个参数context:obj
// 简述:把fn执行,让fn中的this变为obj,并且把除了obj外的剩余参数,依次传递给fn
*/
// fn.call(undefined); //在非严格模式下,context不传递或者传递null/undefined,则this都改为window;严格模式下,不传是undefined,否则传递谁,this就改为谁;
// fn.call(10, 20); //this->Number{10} x->20 y->undefined
// fn.apply(obj, [10, 20]); //this->obj x->10 y->20 apply和call的唯一区别,就是再给fn传递参数的时候,apply需要把所有需要传递的参数信息放在一个数组中,而call是一个个的传递进来即可,但是不论那种办法,最后的结果都是,都是把这些参数一个个的传递给fn
// call的性能要比apply好一些「一丢丢」:尤其是传递三个及以上参数的时候
复制代码
// 获取数据中的最大值
// let arr = [10, 13, 24, 15, 26, 34, 11];
/* arr.sort((a, b) => b - a);
console.log(`数组中的最大值是:${arr[0]}`); */
/* console.log(Math.max(arr)); //NaN
console.log(Math.max(10, 13, 24, 15, 26, 34, 11)); //34
console.log(Math.max(...arr)); //34 */
/* let max = Math.max.apply(Math, arr); //利用apply传递给方法的参数是基于数组完成的
console.log(`数组中的最大值是:${max}`); */
/* let max = 0;
arr.forEach(item => {
item > max ? max = item : null;
});
console.log(`数组中的最大值是:${max}`); */
/* let max = eval(`Math.max(${arr})`);
console.log(`数组中的最大值是:${max}`); */
// 需求:任意数求和「把传递进来的实参(不固定个数)进行求和」
/*
function sum(...params) {
// params->[...] 数组,存储传递的所有实参
return params.reduce((result, item) => {
return result + item;
}, 0);
}
*/
/* Array.prototype.slice = function slice() {
// this->arr
let params = [];
for (let i = 0; i < this.length; i++) {
params.push(this[i]);
}
return params;
};
let arr = [10, 20, 30];
let params = arr.slice(); //把arr克隆 */
function sum() {
// arguments 类数组,不是数组,不能直接用数组的方法(只有数组作为Array的实例才可以直接调用)
// 1.把类数组转换为数组
/* let params = Array.from(arguments);
let params = [...arguments]; */
/* let params = [];
for (let i = 0; i < arguments.length; i++) {
params.push(arguments[i]);
} */
// 鸭子类型:长的像鸭子就称之为鸭子 -> 类数组除了__proto__不是Array.prototype外,它的结构特点和数组基本一样「常规操作处理都是一样的,例如:循环...」 -> 让类数组借用数组原型上的方法,实现一些特定的处理
let params = [].slice.call(arguments);
// Array.prototype.forEach.call(arguments, item => {});
// ...
// 2.求和
return params.length === 0 ? 0 : eval(params.join('+'));
}
let total = sum();
console.log(total); //->0
total = sum(10);
console.log(total); //->10
total = sum(10, 20);
console.log(total); //->30
total = sum(10, 20, 30);
console.log(total); //->60
复制代码
/*
* 重写内置的CALL:把需要执行的函数和需要改变的THIS关联在一起
* + context.xxx=this obj.fn=fn;
* + context.xxx() obj.fn();
*/
Function.prototype.call = function call(context, ...params) {
// this->fn context->obj params->[10,20]
context == null ? context = window : null;
// 需要保证context必须是对象类型的值:只有对象才能设置属性
!/^(object|function)$/.test(typeof context) ? context = Object(context) : null;
let self = this,
result = null,
key = Symbol('KEY'); //新增的属性名保证唯一性,防止污染了原始对象中的成员
context[key] = self;
result = context[key](...params);
delete context[key]; //用完后移除自己新增的属性
return result;
};
function fn(x, y) {
console.log(this, x, y);
}
let obj = {
name: 'zhufeng',
fn: 100
};
fn.call(0, 10, 20);
/*
* 重写内置BIND:柯理化思想「预处理思想」
*/
Function.prototype.bind = function bind(context, ...outerArgs) {
// this->fn context->obj outerArgs->[10,20]
let self = this;
return function (...innerArgs) {
// innerArgs->[ev]
self.call(context, ...outerArgs.concat(innerArgs));
};
};
function fn(x, y, ev) {
console.log(this, x, y, ev);
}
let obj = {
name: 'zhufeng'
};
/* document.body.onclick = function (ev) {
fn.call(obj, 10, 20, ev);
}; */
document.body.onclick = fn.bind(obj, 10, 20);
复制代码
面向对象
构造函数 VS 普通函数
/*
* 编程语言
* 面向对象 OOP java、javascript、php、C#(ASP.NET)、Python、GO、C++、Ruby...
* 面向过程 POP C
* 标记语言:HTML / CSS
*
* =====面向对象编程「对象、类、实例」
* 对象:万物皆对象(泛指)
* 类:对“对象”的划分(按照其功能结构特点,划分出大类和小类)
* 实例:类中具体的事务
*
* JS本身就是基于面向对象思想开发出来的编程语言,所以我们学习和开发JS的时候,也要按照面向对象的思想去处理!!
* 「内置类」
* + 每一种数据类型都有一个自己所属的内置类:Number数字类(每一个数字/NaN/Infinity都是它的实例)、String、Boolean、Symbol、BigInt、Array、RegExp、Date、Function、Object...
* + 每一种DOM元素也都有自己所属的类:
* window -> Window -> WindowProperties -> EventTarget -> Object
* document -> HTMLDocument -> Document -> Node -> EventTarget -> Object
* div -> HTMLDivElement -> HTMLElement -> Element -> Node -> ...
* a -> HTMLAnchorElement -> HTMLElement -> ...
* + HTMLCollection / NodeList / CSSStyleDeclaration / DOMTokenList ...
* + ....
* 学习数组,首先分析一个数组(实例),研究清楚这个实例的特征后(含:结构特点和常用方法等),我们再遇到其他的数组,直接也是按照相同的机制进行处理的
*
* 「自定义类」
* 创建一个函数 fn
* + fn() 普通函数执行「堆栈机制」
* + new fn() 构造函数执行 「堆栈机制 + 面向对象机制」
*/
function Fn(x, y) {
let total = x + y;
this.x = x;
this.y = y;
}
let result = new Fn(10, 20);
console.log(result);
复制代码
对象遍历问题及解决方案
function Fn() {
/*
* EC(FN)
* 初始创建Fn找个类的一个实例对象 0x000
* 初始THIS:this->0x000
*/
let total = 0; //上下文的私有变量 和实例对象没有必然的联系
this.x = 10; //this.xxx=xxx 都是给实例对象设置的私有属性和方法
this.y = 20;
this.say = function () { //0x000.say=0x100 0x001.say=0x101
console.log('SAY');
};
/* 如果不设置返回值,或者返回值是一个基本类型值,默认都会把实例对象 0x000 返回;如果手动返回的是一个引用数据类型值,则以自己返回的为主; */
// return {
// name: 'zhufeng'
// };
}
let f1 = new Fn(); //->0x000
let f2 = new Fn; //->0x001 new执行的时候,如果类不需要传递实参,可以不用加小括号(不加小括号,叫做无参数列表new;设置小括号,叫做带参数列表new;除了是否传递参数的区别,在运算的优先级上也有区别? new Fn->19 new Fn()->20)
// 每一次new都是把函数重新执行(重新形成一个新的私有上下文、重新创建一个实例对象、代码重新执行...)
// console.log(f1, f2, f1 === f2); //=>false
// 检测某个成员(属性/键)是否属于这个对象,或者是否属于这个对象的私有属性
// in:检测成员是否属于这个对象「特点:不论是私有属性,还是公有的属性,只要有则检测结果就是true」
// hasOwnProperty:用来检测当前成员是否为对象的私有属性「特点:只有是私有属性,结果才是ture,哪怕有这个属性,但是属于公有的属性,结果也是false」
// console.log(f1);
// console.log('say' in f1); //->true
// console.log(f1.hasOwnProperty('say')); //->true
// f1是一个对象,他可以访问hasOwnProperty方法并且执行,说明:‘hasOwnProperty’属性是它的一个成员
// console.log('hasOwnProperty' in f1); //->true
// console.log(f1.hasOwnProperty('hasOwnProperty')); //->false 说明‘hasOwnProperty’不是它的私有属性,也就是它的公有属性「前提基于in检测出来的结果是true」
// obj:要检测的对象
// attr:要验证的成员
function hasPubProperty(obj, attr) {
// 思路一:是它的属性 但是还不是私有的,那么一定是公有的「BUG:如果某个属性即是私有的,也是公有的,则检测出来的结果是不准确的」
// return (attr in obj) && (!obj.hasOwnProperty(attr));
// 思路二:真正的思路应该是检测原型上的属性,因为原型上的属性都是公有的
// Object.getPrototypeOf:获取当前对象的原型
let proto = Object.getPrototypeOf(obj);
while (proto) {
// 依次查找原型链,直到找到Object.prototype为止
if (proto.hasOwnProperty(attr)) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
// let sy = Symbol();
// let obj = {
// name: 'zhufeng',
// age: 12,
// 3: '哈哈哈',
// 0: 'zhouxiaotian',
// [sy]: 100
// };
/* console.log(obj);
console.log(obj.hasOwnProperty('name'));
console.log(obj.hasOwnProperty(sy)); //->hasOwnProperty是可以检测Symbol属性的
console.log(sy in obj); //->in也是可以检测Symbol属性的 */
// 很多对“对象”的操作是无法拿到Symbol属性的
// Object.prototype.AAA = 100; //->‘AAA’是obj公共的属性 obj.hasOwnProperty('AAA')->false 'AAA' in obj->true
/* for (let key in obj) {
console.log(key); //->‘name’ ‘AAA’
// for in遍历的时候
// + 无法遍历Symobol的私有属性
// + 但是可以遍历到自己扩展的公共属性「内置的公共属性是不可枚举的(就是无法遍历到的)」
// + 优先遍历数字属性,而且按照从小到大(不会严格按照属性书写的顺序)
} */
/* // 解决:能够避免遍历公共的
for (let key in obj) {
if (!obj.hasOwnProperty(key)) break; //已经遍历到公共的,则私有已经遍历完,结束循环
console.log(key); //->'name'
} */
/* // 解决:只想遍历私有的,包含Symbol的
// Object.keys:获取一个对象非Symbol的私有属性(结果是一个数组,数组中包含获取的属性)
// 类似的还有:Object.getOwnPropertyNames
// Object.getOwnPropertySymbols:只获取Symbol的私有属性(结果也是一个数组)
let keys = [
...Object.getOwnPropertyNames(obj),
...Object.getOwnPropertySymbols(obj)
];
keys.forEach(key => {
console.log(key, obj[key]);
}); */
复制代码
原型和原型链
原型重定向
function Fn() {}
Fn.prototype.x = 100;
Fn.prototype.y = 200;
/* // 缺少constructor && 原始原型对象上的x/y也丢失了
Fn.prototype = {
// getX:function() {}
getX() {},
getY() {}
}; */
/* let proto = {
// 手动设置的constructor是属于可枚举的
constructor: Fn,
getX() {},
getY() {}
};
Fn.prototype = Object.assign({}, Fn.prototype, proto); //->这样合并,最后返回的是一个全新的对象,由于内置的Fn.prototype中的constructor是内置的不可枚举的属性,所以合并后也是无法赋给新对象的 */
/* Fn.prototype = Object.assign(Fn.prototype, {
getX() {},
getY() {}
}); //->这种合并的办法,Fn.prototype还是之前的堆地址,只不过是把新对象中的内容全部扩展到了原始的堆中 */
/*
let obj1 = {
x: 100,
y: 200,
n: {
0: 1,
1: 2
}
};
let obj2 = {
y: 300,
z: 400,
n: {
name: 'zhufeng'
}
};
// Object.assign:合并两个对象「浅比较」
// + 让obj2中的内容替换obj1中的:两者都有的以obj2为主,只有其中一个具备的都是相当于新增...
// + 最后返回的是obj1对象的堆内存地址「相当于改变的是obj1对象中的内容」,并不是返回一个全新的对象...
// let obj = Object.assign(obj1, obj2);
// console.log(obj === obj1); //true
// let obj = Object.assign({}, obj1, obj2);
// console.log(obj); //->全新的对象,也就是assign的第一个参数「新对象」
// console.log(Object.assign(obj1, obj2)); //->浅比较:obj2.n直接覆盖obj1.n
*/
/* let obj = {
fn1() {},
fn2: function fn2() {}
// 两者写法的区别:
// + 第一种写法:obj.fn1函数是没有prototype属性的 「不能被作为构造函数」
// + 第二种写法:和正常的函数没有区别
};
new obj.fn1(); //Uncaught TypeError: obj.fn1 is not a constructor */
复制代码
内置类原型扩展方法
/*
* 向内置类的原型扩展方法
* + 内置类的原型上提供了很多内置方法,但是这些方法不一定完全满足业务需求,此时需要我们自己扩展一些方法
* 「优势」
* + 调用起来方便
* + 可以实现链式写法
* + 限定调取方法的类型,必须是指定类的实例
* + 扩展的方法,各个模块「其他成员」都可以直接的调用
* + ...
* 「弊端」
* + 自己扩展的方法,容易覆盖内置的方法 (解决:自己设定的方法名要设置前缀 myUnique)
* Array.prototype={...} 这样操作是无效的,也怕你一行代码,把数组方法全干没了
* + 基于for in遍历的时候,会把自己扩展到原型上的方法也遍历到
* + ...
*/
/*
function unique(arr) {
let obj = {};
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if (obj.hasOwnProperty(item)) {
// 数组之前出现过这一项,当前项就是重复的,我们此时删除当前项即可
arr.splice(i, 1);
i--;
continue;
}
obj[item] = item;
}
return arr;
}
let arr = [10, 30, 40, 20, 40, 30, 10, 40, 20];
arr = unique(arr);
*/
Array.prototype.unique = function unique() {
// this:一般都是当前要操作的实例(也就是要操作的数组)
let obj = {},
self = this;
for (let i = 0; i < self.length; i++) {
let item = self[i];
if (obj.hasOwnProperty(item)) {
// 数组之前出现过这一项,当前项就是重复的,我们此时删除当前项即可
self.splice(i, 1);
i--;
continue;
}
obj[item] = item;
}
return self; //实现链式写法
};
let arr = [10, 30, 40, 20, 40, 30, 10, 40, 20];
arr.unique().sort((a, b) => a - b).reverse().push('zhufeng'); //执行完成sort返回的是排序后的数组(原始数组也是变的)... 执行完成push返回的是新增后数组的长度「不能再调数组方法了」 => “链式写法”:执行完成一个方法,返回的结果是某个实例,则可以继续调用这个实例所属类原型上的方法...
console.log(arr);
复制代码
重写内置new
/*
function Fn() {
// 创建一个实例对象
// ---- 也会像普通函数执行一样,让其执行 「THIS指向实例对象」
// 返回值没有或者是基本值,则返回的是实例对象...
}
let f1 = new Fn;
*/
// 分析内置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, ...params) {
// 1.创建一个实例对象「创建Ctor类的实例:实例.__proto__ -> 类.prototype」
/* let obj = {};
obj.__proto__ = Ctor.prototype; */
let obj = Object.create(Ctor.prototype);
// 2.把函数执行「THIS指向实例对象」 call->执行函数,改变函数中的THIS
let result = Ctor.call(obj, ...params);
// 3.处理返回值
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
// ----https://www.caniuse.com/
// Object.create([proto]):创建一个空对象,并且让创建的这个空对象的.__proto__指向[proto] “把[proto]作为创建对象的原型”
// let obj = Object.create(); //Uncaught TypeError: Object prototype may only be an Object or null
// let obj = Object.create(Dog.prototype);
// let obj = Object.create(null); //->创建一个空对象,并且阻止了他的__proto__指向「没有这个属性了」
// console.log(obj);
复制代码
函数的多种角色
JQ源码分析
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END