Appearance
1.JS数据类型分类
1.1 分类
基本数据类型: String、Boolean、undefined、null、Number、Symbol,存储在栈中
引用数据类型: Object, 包括Function、Array、Object,存储在堆内存(引用地址存储于栈中)
1.2 差异
基本数据类型:
- 值传递:函数传参时,传递的是值的副本,修改内部值不会影响外部传递的参数变量。
- 栈内存分配:内存空间由系统自动分配和释放,无需手动管理。
引用数据类型:
- 引用传递:函数传参时,传递的是对象的引用地址,修改内部值会影响外部传递的参数变量。
- 堆内存分配:内存空间由 JavaScript 引擎分配,需要手动释放内存,否则可能造成内存泄漏。
2.数据类型的判断
2.1 typeof
typeof返回表示数据类型的字符串, 不能判断null, array等。返回结果包括:number、boolean、string、symbol、object、undefined、function等7种数据类型。
typeof null === 'object' // true
2.2 instanceof 运算符
- 测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性
- 无法检测 null, undefined。
语法:A instanceof B
判断规则:沿着A的_proto_属性这条线来找,同时沿着B的prototype属性这条线,若果两条线能找到同一个引用,即 同一个对象,则返回true。
__proto__是每个对象都有的属性, 构造器的原型, 即__proto__ === constructor.prototype
prototype是函数才有的属性
2.3 constructor属性
- 作用与instanceof运算符相似,但可以检测基本数据类型
- 不稳定,容易被重写,导致检测结果不准确
2.4 Object.prototype.toString.call
Object.prototype.toString.call() 是最准确最常用的方式。
js
const obj = new Object();
Object.prototype.toString.call(obj) === '[object Object]' // true
原型的概念
每个 JavaScript 对象都有一个内置属性,称为prototype(原型)。原型对象本身也是一个对象,它也有自己的原型,依次层层向上,直到遇到一个对象的原型为 null。根据定义,null 没有原型,并作为这个原型链(prototype chain)中的最后一个环节。
原型对象包含了一组属性和方法,这些属性和方法可以被该对象及其子对象继承。
3.浅拷贝、深拷贝
3.1 浅拷贝
- 只复制对象的引用地址,不复制对象本身,新旧对象共享同一块内存。
- 常见实现方式
- Object.assign():需注意的是目标对象只有一层的时候,是深拷贝
- Array.prototype.concat()
- Array.prototype.slice()
js
const obj1 = { name: 'Lilei' };
const obj2 = Object.assign({}, obj1);
obj1.name = 'Hanmeimei';
console.log(obj2.name); // 输出 "Lilei"
3.2 深拷贝
- 复制数据的所有引用结构,在内存中创建两个完全相同又相互独立的数据。
- 常用实现方式:
- lodash 库中的 _.cloneDeep() 方法
- jQuery 库中的 $.extend() 方法
- JSON.parse(JSON.stringify()) 方法
- 手写递归方法
4. 作用域
作用域决定了变量和函数的可访问范围。JavaScript 中存在三种类型的作用域:
- 全局作用域: 在代码的任何地方都可以访问全局作用域中的变量和函数。
- 函数作用域: 在函数内部定义的变量和函数只能在该函数内部访问。
- 块级作用域: 使用 let 和 const 关键字声明的变量只在声明所在的代码块内有效。
JavaScript 代码执行过程中,需要先做变量提升,而之所以需要实现变量提升,是因为JavaScript 代码在执行之前需要先编译。
在编译阶段,变量和函数会被存放到变量环境中,变量的默认值会被设置为undefined;
在代码执行阶段,JavaScript 引擎会从变量环境中去查找自定义的变量和函数。
如果在编译阶段,存在两个相同的函数,那么最终存放在变量环境中的是最后定义的那个,这是因为后定义的会覆盖掉之前定义的。
执行上下文和执行栈
执行上下文:是 JavaScript 执行一段代码时的运行环境,在执行上下文中存在一个变量环境的对象(Viriable Environment),该对象中保存了变量提升的内容。
- 当 JavaScript 执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生存周期内,全局执行上下文只有一份。
- 当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况下,函数执行结束之后,创建的函数执行上下文会被销毁。
- 当使用 eval 函数的时候,eval 的代码也会被编译,并创建执行上下文。
创建执行上下文过程中:
- 变量环境:函数内部通过 var 声明的变量,在编译阶段全都被存放到变量环境里面。
- 词法环境:通过let 声明的变量,在编译阶段会被存放到词法环境(Lexical Environment)中。
执行栈:调用栈是 JavaScript 引擎追踪函数执行的一个机制。
一段代码如果定义了两个相同名字的函数,那么最终生效的是最后一个函数。
如果有形参,先给形参赋值。进行私有作用域中的预解释,函数声明优先级比变量声明高,最后后者会被前者所覆盖,但是可以重新赋值私有作用域中的代码从上到下执行
作用域
作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。
类型: 全局作用域、函数作用域、块级作用域(ES6)
- 全局作用域中的对象在代码中的任何地方都能访问,其生命周期伴随着页面的生命周期。
- 函数作用域就是在函数内部定义的变量或者函数,并且定义的变量或者函数只能在函数内部被访问。函数执行结束之后,函数内部定义的变量会被销毁。
- 块级作用域 ES6通过引入let和const关键字使JavaScript也能像其他语言一样拥有了块级作用域,解决了变量提升所带来的问题。
作用域链
作用域查找变量的链条称为作用域链;作用域链是通过词法作用域来确定的,而词法作用域反映了代码的结构。
每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为outer。
外部引用跟函数调用没有关系,仅跟函数定义时的位置。如下面代码中 bar函数和foo函数的outer引用都指向了全局上下文。
js
function bar(){
console.log(myName);
}
function foo(){
var myName = 'Lilei';
bar();
console.log(myName);
}
var myName='Wangwu';
foo();
当一段代码使用了一个变量时,JavaScript 引擎首先会在“当前的执行上下文”中查找该变量(对于块级作用域:词法环境->变量环境->outer词法环境->outer变量环境...
),如果在当前的变量环境中没有查找到,那么JavaScript 引擎会继续在 outer 所指向的执行上下文中查找。
5. 闭包
在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存。在内存中,我们就把这些变量的集合称为闭包。
**MDN**:
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),
这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。
在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
作用
- 使用闭包可以访问函数中的变量。
- 可以使变量长期保存在内存中,生命周期比较长。
应用场景
- 私有变量和方法
- 模拟类和集成
- 事件处理
- 函数柯里化
回收
闭包不能滥用,否则会导致内存泄露,影响网页的性能。闭包使用完了后,要立即释放资源,将引用变量指向null。
通常,如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄漏。
如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,
判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存。
如果该闭包会一直使用,那么它可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个局部变量。
6. this指针
6.1 this指针指向规则
- 普通函数调用: 在非严格模式下,this 指针指向全局对象 window;在严格模式下,this 指针指向 undefined。
- 方法调用: this 指针指向调用该方法的对象。
- 构造函数调用: this 指针指向通过构造函数创建的新对象。
- 箭头函数: 箭头函数没有自己的 this 指针,而是继承其外层函数的 this 指针。
- node环境中,this指向global
6.2. this 指针的应用场景
- 访问对象属性和方法: 在方法内部,可以使用 this 指针访问该对象的属性和方法。
- 动态创建对象: 可以使用 this 指针在构造函数内部动态创建对象。
- 模拟类和继承: 可以使用 this 指针实现类和继承。
6.3. this 指针的常见问题
- setTimeout 函数中的 this 指针: 无论是否严格模式,setTimeout 函数中的回调函数的 this 指针始终指向 window 对象。
- 事件处理函数中的 this 指针: 在事件处理函数中,this 指针指向事件的目标元素。
对于用户自定义的方法 所有的函数调用都可以转换成apply或call的形式
obj.child.method(p1, p2) 等价于 obj.child.method.call(obj.child, p1, p2)
非严格模式:foo(p1,p2) 等价于 foo.call( window, p1, p2)
严格模式:foo(p1,p2) 等价于 foo.call( undefined, p1, p2)
JS内置的全局方法 如setTimeout,不论是setTimeout()还是window.setTimeout()形式的调用,回调函数内部的this都会指向window
另外
- setTimeout(fn,1000)中的fn中的this,永远都是指向window对象,无论是否严格模式。
- 回调函数中,this指向的是((调用该回调函数)的函数)的调用对象
例如:$("button").click( fn )
// fn里如果有this,指向的是button的DOM对象
非对称加密的主要用途就是:密钥交换和数字签名 数字签名的作用主要是:确保发送的报文没有被篡改 非对称加密主要应用在密钥协商阶段,协商好密钥之后的通信就用对称加密了
原地修改nums数组
Aarray.reduce(function(total, currentValue, currentIndex, arr), initialValue) :方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
函数柯里化
柯里化(Curring),把接受多个参数的函数变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数并且返回结果的新函数.
作用:参数复用、提前确认、延迟执行
js
function curry(y){
return function(x){
return x-y
}
}
curry(1)(4) // 3
柯里化通用封装方法
js
// 两个入参的柯里化函数 初步封装
var currying = function(fn){
let _this = this;
let argfn = Array.prototype.slice.call(arguments, 1);
return function(){
let args = argfn.concat(Array.prototype.slice.call(arguments));
return fn.apply(_this, args);
}
}
//支持多参数, 参数个数大于fn的入参个数时,递归柯里化
function curry(fn, ...args1) {
return function(...args2) {
const args = args1.concat(args2)
if (args.length < fn.length) {
return curry(fn, ...args)
} else {
return fn(...args)
}
}
}
function add(a, b, c) {
return a + b + c
}
const curr = curry(add)
curry性能问题:
1. 存取arguments对象通常要比存取命名参数要慢一点
2. 一些老版本的浏览器在arguments.length的实现上是相当慢的
3. 使用fn.apply( … ) 和 fn.call( … )通常比直接调用fn( … ) 稍微慢点
4. 创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上
大部分应用中,主要的性能瓶颈是在操作DOM节点上,这js的性能损耗基本是可以忽略不计的,所以curry是可以直接放心的使用
QA:
- 看代码执行输出
js
let date = new Date();
setTimeout(()=>{
console.log(new Date() - date);
},1000)
let a = 0;
while((new Date() - date) < 3000){
a++;
}
link: