0. 严格模式
严格模式的概念从ES6引进。通过严格模式,可以在函数内部选择进行较为严格的全局或局部的错误条件检测。
- MDN中严格模式的描述
- 严格模式通过抛出错误来消除了一些原有静默错误
- 严格模式修复了一些导致 JavaScript引擎难以执行优化的缺陷:有时候,相同的代码,严格模式可以比非严格模式下运行得更快
- 严格模式禁用了在ECMAScript的未来版本中可能会定义的一些语法
- 使用方法
- 脚本最顶端放
"use strict"
- 支持严格模式的引擎会启动这种模式
- 不支持该模式的引擎就当遇到了一个未赋值的字符串字面量,会忽略这个编译指示
- 函数中使用
function striceExample(){ "use strict"; // 其他逻辑...... }
- 现代ES6的代码,支持 classes 和 modules 高级语言结构,它们会自动启用 use strict。因此,如果我们使用它们,则无需添加 “use strict” 指令
- 脚本最顶端放
- 严格和非严格的区别
- 严格模式下变量需声明才能用,否则报错“ReferenceError”
- 严格模式下禁止使用with语句
因with语句无法在编译时确定属性归于什么对象,严格模式有利于编译效率提高
// NOTE with的用意是逐级的对象访问,即在指定代码区域,通过节点名称调用对象; // 通常被当做重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身 with(location){ console.log(href);}// 严格模式:抛出语法错误 // p.s., with的应用场景 let obj={ a:1, b:2, c:3 } // 修改obj的一般写法 obj.a = 3; obj.b = 5; // 使用with的写法 // 在with代码内部,每个变量首先被认为是一个局部变量,如果局部变量与 obj 对象的某个属性同名,则这个局部变量会指向 obj 对象属性。 with(obj){ a = 3; b = 4; c = 5; } // p.p.s, with的弊端 // 1. 数据泄漏 function foo(obj) { with (obj) { a = 5; } } var o1 = { a: 3 }; var o2 = { b: 3 } foo(o1); console.log(o1.a); //2 foo(o2); console.log(o2.a); //undefined console.log(a); //5,(由于o2传递)a被泄漏到全局作用域上。传递o2时,因为非严格模式下LHS找不到变量会创建全局变量,因此全局中会新建变量a // 2. 性能下降 // JS若在代码中发现了with,只能简单地假设关于标识符位置的判断都是无效的,因为无法知道传递给 with 用来创建新词法作用域的对象的内容到底是什么。 // 而优化依赖于静态分析。因此,无法分析的引擎可能不做优化,使代码运行变慢(eval()也会)
- 严格模式下创建eval作用域
- 严格模式下禁止this指向全局对象
- 严格模式下禁止在函数内部遍历调用栈
- 严格模式下无法删除变量
- 严格模式显示报错
- 严格模式下不允许对禁止扩展的对象添加新属性
1. this指向
1)绝大多数情况下,非箭头函数内部的this由调用者确定,即谁调用指向谁。
2)箭头函数的this取决于函数定义所在的上下文中this,即函数定义外部this是什么,箭头函数内部的this就是什么,相当于固化了当前执行环境中的this,注意不是函数定义所在的对象!
3)构造函数中的this指向新创建的对象
4)apply/call方式调用的函数,其this可以在调用时指定
5)bind函数可以将根据一个函数生成一个新的函数,并且该函数的this绑定到指定的对象上
- 一般情况,函数中的this指向函数的调用者
p.s.没指定调用者,严格模式下this会是undefined,非严格模式下则是指向全局对象(Node环境中是global,浏览器环境中则是window)
// 调用者window
function demo1(){
console.log(this)
}
// 调用者demo2
let demo2={
log(){
}
}
// 调用者setTimeout方法
var demo3= {
log() {
setTimeout(function print(){ // 给一个函数名方便理解
console.log(this)
},1000)
}
}
- 构造函数中的this指向新对象
function Demo() {
this.a = 1;
console.log(this)
}
new Demo() // {a: 1}
apply
,call
中的this指向target。
此时,第一个参数理解为this=target,区别在于apply的第二个参数是将数组拆成一个个参数传入([1,2,3]
变为log(1,2,3)
)、call将参数传入函数。
p.s.非严格模式下,call
的参数如果null
或undefined
,则指向全局;严格模式下,依然指向给定null
或undefined
。
p.p.s.,对箭头函数使用call或apply将不能指定this,因箭头函数的this在定义时已确定
function log() {
console.log(this, arguments)
}
var target = {a: 1}
log.apply(target, [1,2,3]) // {a:1} [1,2,3]
log.call(target, 1, 2, 3) // {a:1} [1,2,3]
bind
和箭头函数
bind和箭头函数其实是一种,后者作为语法糖简化了bind。bind作用是绑定函数作用域但不立即执行。函数this在定义时就已确定,指向此时外层的this。
p.s.若没有外层函数,在严格/非严格模式下都指向全局对象而非undefined。
"use strict"
function log() {
console.log(this, arguments)
}
var target = {a: 1}
log.bind(target)
log(1, 2, 3) // {a:1} [1,2,3]
- arguments传参时的this:
- arguments是一个对象
- 由于js对象允许函数有不定数目的参数,所以有一种机制,可以在函数体内读取所有参数,即arguments对象的由来
- arguments包含运行时的参数,arguments[0]
- 定义
- callee属性:返回对应原函数
var f = function() { console.log(arguments.callee===f); } f()
- 通过
- 严格模式
- arguments对象与函数不具有联动关系,即修改不会影响到实际的函参
var f = function(a, b) { 'use strict'; // 严格 arguments[0] = 3; arguments[1] = 2; return a+b; } f(1, 1)
- 命令接受一个字符串作为参数,语句执行
-
`eval(‘var a=1;’)
-
静态分析阶段
-
1
-
- 相关代码
var length = 10; function fn() { console.log(this.length); } var obj = { method: function(fn) { fn(); arguments[0](); } }; obj.method(fn, 1); // 输出:10,2 // 第一个值10,由fn()直接得到这里this指向的window。所以输出的值为最外层定义的length // 第二个值2,执行的是method里面的第二行"arguments0"(arguments0 => fn() ),这里this执行的是arguments这个对象,所以输出值为arguments的长度
2. 闭包
闭包是有权访问另一个函数作用域中变量的函数,即在函数内创建另一个函数。实际上闭包 = 里层函数+外层函数共同构成
let book = (function(){
// 静态私有变量
let bookNum = 0
// 静态私有方法
function checkBook(name){
console.log(bookNum) // Closure闭包bookNum: 0
}
f()
})
- 闭包使用return的情况:外部需要引用闭包变量
let book = (function(){
let bookNum = 0
return function(){
console.log(a)
}
})
const fn = book()
fn()
- 应用情况:保证数据的私有,避免同名全局变量的影响;外部能使用但无法修改 -> 会引起内存泄漏,但还想在外部进行访问
1)基本应用
function count(){
let i=1
function fn(){
i++
console.log(`函数被调用了${i}次`) //注意不是''而是``
}
return fn
}
const fun = count() // Attention! Count()中的i被引用后不会被回收
fun() // 函数被调用了2次
i = 1000 //即使修改了i也不会影响到函数中
2)模仿块级作用域
// 计时器的应用
for (var i = 0; i < 10; i++) {
(function (j) {
setTimeout(function () {
console.log(j);
}, 1000 * j)
})(i)
}
// 相当于闭包的基本格式
for (var i = 0; i < 10; i++) {
function (j) {
setTimeout(function () {
console.log(j);
}, 1000 * j)
}
return i
}
// 打印一组li的下标
for(let i=0; i<lis.length; i++){
(function(j){
lis[j].onclick = function(){
alert(kj)
}
})(i)
}
3)埋点计数器:网站中常用的数据采集方法
function count(){
let num = 0
return function(){
return ++num
}
}
// 第一个需要统计的地方
let getNum_1 = count()
// 第二个需要统计的地方
let getNum_2 = count()
// 若统计两个button的点击次数
document.querySelectorAll('common-btn')[0].onclick = function(){
console.log('点击按钮1的次数:'+getNum());
}
document.querySelector('submit-btn').onclick = function(){
console.log('点击按钮1的次数:'+getNum());
}
4)柯里化:将一个多参数转为单参数函数
柯里化和类的区别:
① 柯里化是一种函数式编程技术,目前是将函数的参数分解为多个可部分应用的函数,以实现更灵活的函数组合
② 类是面向对象编程,以创建多个对象实例,每个实例都共享类的属性和方法
方法不同,看上去相似
p.s. 关于性能消耗
柯里化通常涉及创建多个函数闭包,这可能会导致一些内存消耗。每个柯里化的函数都需要维护它自己的作用域和参数。但在许多情况下,这种额外的开销是微不足道的,不会显著影响性能。
类也涉及创建对象实例,每个实例都具有自己的属性和方法。这也会导致一些内存消耗,但这通常取决于类的复杂性和对象的数量。在某些情况下,类可能会更有效,因为它们利用了JavaScript引擎的原型继承机制(原型所有属性和方法能被实例对象共享),而不是每个对象都有自己的副本。
总的来说,性能差异通常不是决定柯里化和类之间选择的主要因素。更重要的是根据你的具体需求和代码结构来选择适当的编程技巧。优化性能通常需要更具体的优化策略,而不仅仅是选择柯里化或类。
// 原函数
function check(reg, txt){
return reg.test(txt)
}
console.log(check(电话号码的正则,13923456789))
console.log(check(邮箱的正则,youxiang@163.com))
// 更新
function nowCheck(reg){
return function(txt){
return reg.test(txt)
}
}
var isPhone = nowCheck(电话号码的正则)
console.log(isPhone('13923456789'))
var isEmail = nowCheck(邮箱的正则)
console.log(isEmail('youxiang@163.com'))
2.1 类的基本方法和闭包
- 创建类的方法
// 方法1
// 1)首先声明一个函数,保存在变量里
// 2)在函数的内部通过对this变量添加属性/方法实现对类添加属性/方法
let AClass = function(id, name){
// this和prototype创建的区别:每创建一个新对象时,this的方法会创建,而原型(链)不会
this.id = id
this.bookName = name
}
// 方法2:在类的原型上添加属性和方法
// 1)选择1-单独为原型对象属性赋值
BClass.prototype.display = function(){}
// 2)选择2-将对象赋值给类的原型对象
BClass.prototype ={
display: function(){}
}
- 抽象类、抽象类的原型和实际类的关系
!js中,constructor只是抽象类原型的属性
- 类进行属性和方法的封装【全】
类具有3个部分,第一部分是构造函数内的(用于创建和初始化实例对象时使用的属性和方法),第二是构造函数外的、通过点语法对类添加的(只能类用,对象不能),第三是原型对象prototype(第一部分会成为prototype的constructor)
// 对象:类 => 【part 1. 构造函数内】
let AClass = function(id, name){
// 私有属性和方法
var num = 1
function checkId(){}
// this创建的:外部可访问
// 对象公有属性
this.id = id
// 特权方法:能访问私有属性
this.getId = function(){}
// 对象公有方法:不能访问私有属性
this.copy = function(){}
this
// 构造器,也是this
this.setName(name)
}
// 类添加(对象不能访问) => 【part 2. 构造函数外】
AClass.isChinese = true // 类静态公有属性
AClass.resetTime = function(){} // 类静态公有方法
// prototype添加(对象可以访问),因新对象的prototype和类的prototype指向同一个对象 => 【part 3. 原型对象】
AClass.prototype = {
// 公有属性
isJS: true,
// 公有方法
display: function(){}
}
类的私有属性num
和静态共有属性resetTime
无法被新对象访问
- 闭包实现【全】
let Book = function(){
if(){
}else{
}
}
- 用法
- includes用法
1)数组
includes能判断一个数组中是否涵盖某个元素,并返回true或false。
可以含2个参数,第2个参数表示判断的起始位置;若['a', 'b', 'c'].includes('a')
2)字符串
查找字符串是否包含"Runoob",
let str = "Hello World"
2. - includes用法
2.2 创建对象的安全模式
- 没有使用new创建类的对象
对象是undefined,而参数会添加到window上
Why? new关键字可以看作当前对象this的赋值,但
// 图书类
let Book = function(title, time){
this.title = title
this.time = time
}
// 虚假的实例化
let aBook = Book('JS设计模式','2014')
console.log(aBook) // undefined
console.log(window.title) // JS设计模式
- 限定只能使用new
let winTool = function(){ // 创建对象的安全模式:限定new if(this instanceof winTool){ console.log('符合条件') }else{ return new winTool() } // 其他设计 }
2.3 继承
- 类式继承
3. 原型
3.1 基本概念
- 原型:每个对象都有prototype属性(隐式引用),称为原型
- 别称-原型对象:由于这个属性的值是对象,也成为原型对象
- 作用
- 存放属性和方法
- 在js中实现继承
- 基于原型链的继承
- JS对象是动态属性(自有属性)“包”
- 当试图访问对象属性时,
- JS: 有一个指向原型对象的“链”
- child.value
- JS对象是动态属性(自有属性)“包”
- 基于原型链的继承
- 当前继承对象:继承“方法
const parent = { value: 2, method() { return this.value+1; } } console.log(parent.method()) // 3 // 当调用parent.method时,"this"指向parent const child = { __proto__:parent // 继承parent对象 } console.log(child.method()) // 3 function Box(value) { this.value = value } // this值指向当前继承的对象,而非拥有该函数的原型对象 // 构造函数:原型的强大之处,若一组应该出现在每个实例上 const boxes = [{value: 1, getValue() {}}, {value: 1, getValue() {}}, {value: 1, getValue() {}}] child.value = 4; // 在child,将"value"属 // JS const boxPrototype = { getValue() { return this.value; } } const boxes = [{ },{ },{ }]
- 隐式引用的含义:ECMAScript 规范说 prototype 应当是一个隐式引用,通过Object.getPrototypeOf(obj) 间接访问指定对象的 prototype 对象而Object.setPrototypeOf(obj, anotherObj) 间接设置。部分浏览器提前开了
__proto__
的口子,使得可以通过obj.__proto__
直接访问原型,通过obj.__proto__ = anotherObj
直接设置原型。ECMAScript 2015 规范只好向事实低头,将__proto__
属性纳入了规范的一部分。
// 声明 let obj = {a:1,b:} // 控制台看到的 // a, b, __proto__
- 原型和原型链
const arr = new Array(1,2,3)
arr.reverse() // 翻转
相当于构造函数具有的原型Array.prototype中方法能给实例arr使用,此处arr.__proto
指向Array.prototype
,而Array.__proto
指向更上一级的原型即Object.prototype
,最终Object.prototype
指向null
。对象能使用该原型链之上的属性和方法。
3.2 原型链的应用
- 不污染对象的链式添加
1) 函数调用方式
// 会污染Function:将checkEmail方法添加到所有的函数对象上,包括原生的函数对象和自定义的函数对象
Function.prototype.checkEmail = function(){
// 逻辑
}
let f = function(){}
f.checkEmail
// 不污染Function:将方法添加到Function.prototype,、
Function.prototype.addMethod = function(name, fn){
this[name] = fn
return this // 实现链式添加
}
let f2 = function(){}
f2.addMethod('checkName', function(){}) // 非链式添加
f2.addMethod('checkPassword', function(){
// 每个方法加上return this,实现链式调用
}) .addMethod('checkEmail', function(){
// return this
}) // 链式添加
f2.checkName()
2)类式调用
Function.prototype.addMethod = function(name, fn){
this.prototype[name] = fn
// 无需return this即可链式调用创建
}
let Method = function(){}
Method.addMethod('checkPassword', function(){
}) .addMethod('checkEmail',function(){
})
let m = new Method()
m.checkEmail()
3)函数的嵌套调用
UINT Fac(UINT nParam) {
if(nParam < 2)
return 1
else
return nParam *Fac(nParam-1)
}
4)函数指针变量
指针变量可以指向一个函数,该函数在被编译时分配给一个入口地址。这个入口地址被称为函数指针,其一般形式为:类型(*函数指针变量名)(参数列表,…)
#include <stdio.h>
#include <windows.h>
typedef void (*FN_NOTIFY)(const char *pszMsg)
void Notify1(const char *pszMsg) {
printf("Notify1:%s\n", pszMsg)
}
void Notify2(const char *pszMsg) {
printf("Notify1:%s\n", pszMsg)
}
5)
3.3 原型链的应用
let age = 18;
let flag =
4. 函数的分类
函数分为一级函数、一阶函数、高阶函数、一元函数、柯里化函数、纯函数。
4.1 一级(等)函数
函数是第一类对象,一等函数表示该语言的函数可以被视为任何其他变量,像任何其他值一样处理:也就是说,它们可以在运行时被动态地分配给一个名称或符号。它们可以存储在数据结构中,通过函数参数传入,并作为函数返回值返回。
// 如以下分配给监听器的处理函数
const handler = () => console.log('???')
document.addEventListener('click', handler)
- addEventListener方法参数
- 方法参数
const elem = document.getElementById('myElem') element.addEventListener('click', function() { console.log('Element clicked!') }, { capture: false, once: false, passive: false })
4.2.1 一阶函数
一阶函数是不接受另一个函数作为参数且不返回函数作为其返回值的函数。
const firstOrder = () => console.log("I am a first order function!")
- 函数名
- 1
4.2.2 高阶函数
高阶函数是接受另一个函数作为参数或返回函数作为其返回值的函数,或二者兼有
- 参数的理解
- 箭头函数中的参数
5. ECMAScript
5.1 和JS的关系
- ECMAScript和JavaScript的关系
- ECMAScript:1996年11月,创造者Netscape公司
- JavaScript:
- 脚本语言轻量级script language:
- 不具备开发操作系统的能力
- 独立的数据类型
- 采用基于原型对象prototype的继承链
- JavaScript语言:解释器直接执行
- 嵌入式(embedded):
- JavaScript核心语法 - 轻量级的脚本语言,
- 循序渐进、由浅入深,
- 脚本语言轻量级script language:
- 信息结构
- 观点信息
- 属性
- 通过获取大量信息
- 需要获取模式
- 而非记忆细节
- 处理方式
- 4速读方法
- 5图表法
- 属性
- 过程信息
- 定义:知道人行
- 观点信息
5.2 ES5
- 1
- 1
- 1
5.3 ES6引入的新特性
- 执行环境、作用域(Scope)和上下文(Context)
- 作用域基于函数\,上下文基于对象
- 执行环境(execution context->scope):JS单线程意味同时只能执行一个任务;当js解释器初始化时,默认进入全局执行环境
- 定义:偏向作用域
- 变量或函数有权访问的其他数据,决定行为
- 特点
- 每个函数都有自己的执行环境 - 每次调用都会创建新环境
- 环境会推入环境栈(execution stack)内
- 两个阶段
- 创建:每个执行环境都有一个变量对象(variable object/activation object)-执行环境中的变量、函数声明和参数组成,作用域会被初始化
this
也会被确定- 只有解析器能访问
- 执行:解释执行
- 会创建作用域链(the scope chain):对execution context内所有变量函数的有序访问
- 执行上下文的词法环境,外部环境outer指向的词法
- 当前执行上下文的词法环境(在全局对象的属性a访问不到)会添加到作用域的最前端并递归处理外部,直到全局执行上下文-全局:最后一个结点
- 作用域:词法环境
- 全局都是作用域链的最后一个对象
- 查找变量时,沿着作用域链查找,找不到抛出ReferenceError(如let的块级作用域)
- 代码
var color = "blue"; function changeColor(){ var anotherColor = "red"; function swapColors(){ var tempColor = anotherColor; anotherColor = color; color = tempColor; // 这里可以访问color, anotherColor, 和 tempColor } // 这里可以访问color 和 anotherColor,但是不能访问 tempColor swapColors(); } changeColor(); // 这里只能访问color console.log("Color is now " + color);
- 会创建作用域链(the scope chain):对execution context内所有变量函数的有序访问
- 创建:每个执行环境都有一个变量对象(variable object/activation object)-执行环境中的变量、函数声明和参数组成,作用域会被初始化
- 定义:偏向作用域
- 作用域:涉及被调用函数的变量访问
- 函数外都是全局的!
- 任何定义的全局变量,意味着它要在函数体外部被声明
- 存活于整个运行时
- 在ES6前,JS不支持块级作用域
- ES5中,变量只有
var
和function
两种- var将全局变量提升到作用域最顶端,内部则是函数作用域最顶端
- =在
if
、switch
、for
、while
循环中不支持块级 - ES6通过
let
定义变量,修正了var
的缺点// ES5 function func() { if(true) { var tmp = 123; } console.log(tmp); //123 } // ES6 function func() { if (true) { let tmp = 123; } console.log(tmp); // ReferenceError: tmp is not defined }
- ES5中,变量只有
this
上下文:函数作为对象中一个方法被调用时,this
是在该方法的对象上
调用函数使用var obj = { foo: function() { alert(this === obj); } } obj.foo(); // true
new
操作符创建对象实例的情况下,作用域内this
的值设为新创建实例
4.
5. 运行时(runtime)期间变量的不同作用域范围 - 函数外都是全局的!
- 上下文:
this
的值
- ES5和ES6的区别
- let和const关键字
- ES5只有var关键字声明变量,而ES6提供了let、const,二者都允许块级作用域,而var只有函数作用域
- ES6转为ES5:
let tempFunc =
- 箭头函数
ES6提供了箭头函数,更简洁- 外形不同:箭头函数用箭头定义,普通函数中没
- 普通函数:arguments对象
- 存储实际传递的参数
- 箭头没有arguments对象
- 也没有super、new.target
- 箭头函数都是匿名;普通函数可有匿名,或具名
使用new Function// 具名函数 function xxx() { // 要执行的代码 typeof 37 === 'number' typeof 3.14 === 'number' typeof Math.LN2 === 'number' } xxx() // 直接调用 xxx.onclick = fn // 在事件中的调用 // 匿名函数:没有名字,使用时只有两种情况 // 1)匿名函数自执行 (function(){ console.log('匿名函数自执行') })() // 2)将函数存到变量 function saveParams(a, b){ return a + b } result = saveParams(3, 5) print(result) // 箭头函数 let tmpFunc = () => { console.log(arguments) } tmpFunc()
局部变量let 函数名 = new Function('形参,形参1','函数体') // 定义 var sayBye = new Function('console.log("bye!")') // 调用 sayBye() // window.name = '长袖' // 传参 let func1 = function() { return this.name } let func2 = () => this.name let temp = { name: } // void Process(FN_NOTIFY pFn) { const int MAX_LOOP = 100 } // 箭头函数 var name = "长袖" let tempFunc = () => { console.log(this.name) } console.log(func1.call(temp)) console.log(func1.call(temp))
void foo(int i1, int i2) { int i3, i4; i3 = 3; i4 = 4; { int i3, i5; i3 = 3333; i5 = 5; cout << "in block:" << i3 << endl } }
- 箭头函数不能用于构造;普通可用于构造,创建对象实例
- 箭头函数没有this,其this通过作用域查到外层作用域的this,且指向函数定义时的this而非执行时
- 箭头函数不具有arguments对象,如果要用使用rest参数
- 箭头函数不具prototype原型对象,箭头函数不具super
- 箭头函数不能用yield命令,因此不能用作Generator函数
- 不能用call/apply/bind修改this指向,但能通过外层作用域的this间接修改
- 箭头函数没有arguments对象,不能用new,否则会报错
- 模板字符串(template string)
ES6引入了模板字符串,允许在字符串中插入变量和表达式。即用反引号`标识,当普通字符串/定义多行字符串/在字符串中嵌入变量 - 解构赋值
ES6引入了解构赋值,允许从数组/对象中提取值并付给变量 - 类
ES6引入了类,使JS更面向对象
1)
2) - Promise
ES6引入Promise,提供一种更好的异步编程方式,用于处理异步 - 模块化
ES6提供了标准的模块化方式,使JS开发更规范并易于维护
- let和const关键字