当函数执行时,去创建一个称为「执行上下文(execution contex)」的环境,分为 创建和执行 两个阶段
是指 函数被调用但未被执行任何代码时,此时创建了一个拥有3个属性的对象:
主要工作:1、分配变量、函数的饮用、赋值 2、执行代码
js中有全局作用域、函数作用域,es6中又增加了 块级作用域。 作用域最大的用途就是 隔离变量或函数,并控制他们的生命周期。 作用域是在函数执行上下文创建时定义好的,不是函数执行时定义的。
当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。如果在当前作用域无法找到会向上一级作用域寻找,直到找到或抵达全局作用域,这样的链式关系就是 作用域链(Scope Chain)
可以访问其他函数作用域中(内部)变量的函数
什么节点循环绑定事件,解决方案 立即执行函数、var => let、
1、函数直接调用时
function myfunc() { console.log(this) // this是widow } var a = 1; myfunc(); // 常考点 function show () { console.log('this:', this); // this是window } var obj = { show: function() { show(); // 函数被直接调用了 } }; obj.show() 复制代码
2、函数被别人调用时
function myfunc(){ console.log(this) // this是对象a } var a = { myfunc: myfunc } a.myfunc(); 复制代码
3、new一个实例时(构造函数)
function Penson(name) { this.name = name; console.log(this); // this是实例p } var p = new Penson('this:::'); 复制代码
4、apply、call、bind
function getColor(color) { this.color = color; console.log(this); } function Car(name, color){ this.name = name; // this指的是实例car getColor.call(this, color); // 这⾥的this从原本的getColor,变成了car } var car = new Car('卡⻋', '绿⾊'); 复制代码
5、箭头函数时
// 箭头函数 var a = { myfunc: function() { setTimeout(() => { console.log(this); // this是a }, 0) } }; a.myfunc(); 复制代码
1、对于直接调⽤的函数来说,不管函数被放在了什么地⽅,this都是window 2、对于被别⼈调⽤的函数来说,被谁点出来的,this就是谁 3、在构造函数中,类中(函数体中)出现的 this.xxx = xxx 中的 this是当前类的⼀个实例 4、call、apply时,this是第⼀个参数。bind要优于call/apply哦,call参数多,apply参数少 5、箭头函数没有⾃⼰的this,需要看其外层是否有函数,如果有,外层函数的this就是内部箭头函数 的this,如果没有,则this是window
call()、apply()、bind() 都是用来重定义 this 这个对象的!
obj.myFun.call(db,'string1', ... ,'stringN' )
obj.myFun.apply(db,['成都',...,'string' ])
本身就是一个函数,为了规范一般将首字母大写,区别在于 使用 new 生成实例的函数就是构造函数,直接调用的就是 普通函数。
每个对象都有其原型对象,对象从原型那继承属性和方法,这些属性和方法定义在 对象的构造函数的 prototype 属性上,而非对象实例本身。
简单来讲,浅拷贝 之解决了 第一层问题 基本类型值和引用类型地址
展开语法 Spread ...
let a = { name: "ceshi", book: { title: "You Don't Know JS", price: "45" } } let b = {...a}; console.log(b); // { // name: "ceshi", // book: {title: "You Don't Know JS", price: "45"} // } 复制代码
拷贝所有属性以及指向得动态分配的内存。拷贝前后两个 对象互不影响。
-- | 和原数据是否指向同一对象 | 第一层数据为基本数据类型 | 原数据中包含子对象 |
---|---|---|---|
赋值 | 是 | 改变会使原数据一同改变 | 改变会使原数据一同改变 |
浅拷贝 | 否 | 改变不会使原数据一同改变 | 改变会使原数据一同改变 |
深拷贝 | 否 | 改变不会使原数据一同改变 | 改变不会使原数据一同改变 |
思路:浅拷贝 + 递归,浅拷贝时 判断属性值是否是对象,是对象就进行递归操作。
export function deepClone(source) { if (!source && typeof source !== 'object') { throw new Error('error arguments', 'deepClone') } const targetObj = source.constructor === Array ? [] : {} Object.keys(source).forEach(keys => { if (source[keys] && typeof source[keys] === 'object') { targetObj[keys] = deepClone(source[keys]) } else { targetObj[keys] = source[keys] } }) return targetObj } 复制代码
某个函数在一段时间内无论被触发了多少次,都只执行最后一次。(会延长触发时间)
实现原理是利用定时器,函数第一次执行时设置一个定时器,调用时 发现已有定时器那么清空之前定时器从新设置一个新的定时器,直到最后一个定时器到时触发函数执行。
function debounce(fn, awit = 50) { let timer = null return function(...args) { // 存在定时器则清空 if (timer) clearTimeout(timer) // 第一次设置定时器 timer = setTimeout(() => { fn.apply(this, args) }, awit); } } const ceshiFnDeb = debounce(()=> console.log('ceshiFnDeb防抖函数执行了'), 1000) document.addEventListener('scroll', ceshiFnDeb) 复制代码
函数节流指的是 某个函数在 指定时间内(n秒)只执行一次,不管后面函数如何调用请求,不会延长时间间隔,n秒间隔结束后 第一次遇到 新的函数调用会触发执行,以此类推。
// 节流 throttle: 固定间隔时间内只调用一次函数 // 思路1:根据两个时间戳来对比 function throttle (fn, awit = 50) { let startTime = 0 return function(...args){ let nowTime = +new Date() // 当前时间和上次时间做对比,大于间隔时间的话,就可以执行函数fn if (nowTime - startTime > awit) { // 执行完函数之后重置初始时间,等于最后一次触发的时间 startTime = nowTime fn.apply(this, args) } } } // 执行 throttle 函数返回新函数 const ceshiFn = throttle(()=> console.log('ceshiFn节流函数执行了'), 2000) // 每 10 毫秒执行一次 betterFn 函数,但是只有时间差大于 1000 时才会执行 fn setInterval(ceshiFn, 10); 复制代码
不过市面上比较全的还是 lodash的防抖节流函数 **
script全部代码、setTimeOut、setInterval、I/O、
Promise、Process.nextTick(Node独有)
这里归纳3个重点
本身是一个构造函数,自身有 all、reject、resolve方法,原型上有 then、catch方法
promise的then可以接受两个函数,第一个resolve,第二个参数为reject
函数返回一个 Promise对象,如果内部发生异常则会导致返回的 Promise 对象状态变为 reject
状态。抛出的错误会被 catch
方法接收到。
async
函数内部 return 返回的值。会成为 then
方法回调函数的参数。
正常情况下,await 命令后面跟着的是Promise对象,返回对象的结果,如果不是的话,就直接返回对应的值。
Object.defineProperty()
Vue 2.x 利用 Object.defineProperty(),并且把内部解耦为 Observer, Dep, 并使用 Watcher 相连
Proxy
Vue 在 3.x 版本之后改用 Proxy 进行实现
为了解决两大痛点: 1、每个模块都要有自己的 变量作用域,两个模块之间的内部变量不会产生冲突。 2、不同模块之间保留相互 导入和导出的方式方法,模块之间能够相互通信,模块的执行与加载遵循一定的规范,能保证彼此之间的依赖关系。
模块化开发的4个好处:
是服务器端模块的规范,Node.js采用了这个规范。
每个JS文件就是一个模块(module),每个模块内部使用 require
函数和 module.exports
对象来对模块进行导入和导出
适合web开发的模块化规范
模块文件中,我们使用 define 函数定义一个模块,在回调函数中接受定义组件内容。这个回调函数接受一个 require 方法,能够在组件内部加载其他模块,这里我们分别传入 模块ID,就能加载对应文件内的AMD模块。
// moduleA.js define(function(require) { var m = require('moduleB'); setTimeout(() => console.log(m), 1000); }); // moduleB.js define(function(require) { var m = new Date().getTime(); return m; }); // index.js require(['moduleA', 'moduleB'], function(moduleA, moduleB) { console.log(moduleB); }); 复制代码
同时被 CommonJs规范和AMD规范加载的UMD模块,一套同时适用于node.js和web环境
UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。 在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。
ES6 为导入(importing)导出(exporting)模块带来了很多可能性
与前两者的最大区别在于,ESModule是由JS解释器实现,而后两者是 在宿主环境中运行时实现。ESModule导入实际上实在语法层面新增了一个语句,而AMD和ComminJs加载模块实际上是调用了 require函数。
正则表达式可以从一个基础字符串中根据一定的匹配模式替换文本中的字符串、验证表单、提取字符串等等。
最全正则表达式链接
正则表达式主要依赖于元字符。 元字符不代表他们本身的字面意思,他们都有特殊的含义。一些元字符写在方括号中的时候有一些特殊的意思。以下是一些元字符的介绍:
元字符 | 描述 |
---|---|
. | 句号匹配任意单个字符除了换行符。 |
[ ] | 字符种类。匹配方括号内的任意字符。 |
[^ ] | 否定的字符种类。匹配除了方括号里的任意字符 |
* | 匹配>=0个重复的在*号之前的字符。 |
+ | 匹配>=1个重复的+号前的字符。 |
? | 标记?之前的字符为可选. |
{n,m} | 匹配num个大括号之间的字符 (n <= num <= m). |
(xyz) | 字符集,匹配与 xyz 完全相等的字符串. |
| | 或运算符,匹配符号前或后的字符. |
\ | 转义字符,用于匹配一些保留的字符 [ ] ( ) { } . * + ? ^ $ \ | |
^ | 从开始行开始匹配. |
$ | 从末端开始匹配. |