JS内功修炼-基础篇
梳理一下基础知识:
1. JS数据类型
js数据类型分为:基础数据类型(7种)和引用数据类型object。
基础数据类型: null、undefined、 number、 string、 boolean 、symbol(es6)、 bigint(可以表示大于2^53 -1的整数)
引用数据类型:object。object包括array、function、Date、RegExp等
2. 判断数据类型的方式:
(1) typeof:
用来判断除了null的基本数据类型,typeof null返回object.
引用数据类型除function外,都返回object
var a = 1;
var b = 'lala';
var c = true;
var d = undefined;
var e = Symbol();
var f = 1n;var g = null;
var h = {};
var i = [];
var j = function() {};console.log(typeof a); // number
console.log(typeof b); // string
console.log(typeof c); // boolean
console.log(typeof d); // undefined
console.log(typeof e); // symbol
console.log(typeof f); // bigintconsole.log(typeof g); // object
console.log(typeof h); // object
console.log(typeof i); // object
console.log(typeof j); // function
(2)instanceof
用来判断引用数据类型,类实例,instanceof会沿着原型链一直往上寻找,返回值为true或false
var h = {};
var i = [];
var j = function() {};
var k = new Date();
var l = /[0-9]/g;console.log(h instanceof Object); // trye
console.log(i instanceof Array); // true
console.log(j instanceof Function); // true
console.log(k instanceof Date); // true
console.log(l instanceof RegExp); // true
(3) Object.prototype.toString.call()
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call('lala');// "[object String]"
Object.prototype.toString.call(1);// "[object Number]"
Object.prototype.toString.call(true);// "[object Boolean]"
Object.prototype.toString.call(new Date());// "[object Date]"
Object.prototype.toString.call(/[0-9]/);// "[object RegExp]"
手写instanceof
function myInstanceof(left, right) {// 这里先用typeof来判断基础数据类型,如果是,直接返回falseif(typeof left !== 'object' || left === null) return false;// getProtypeOf是Object对象自带的API,能够拿到参数的原型对象let proto = Object.getPrototypeOf(left);while(true) { //循环往下寻找,直到找到相同的原型对象if(proto === null) return false;if(proto === right.prototype) return true;//找到相同原型对象,返回trueproto = Object.getPrototypeof(proto);}
}// 验证一下自己实现的myInstanceof是否OK
console.log(myInstanceof(new Number(123), Number)); // true
console.log(myInstanceof(123, Number));
3. 深浅拷贝
这要从数据的在内存中存储的位置说起,基本数据类型存储在栈中,引用数据类型数据存储在堆中,栈中存储了访问对象的地址。
对于基础数据类型来讲,都是对值的拷贝;
但是对引用数据类型来讲,分为深拷贝和浅拷贝。
浅拷贝首先会创建一个对象,对原对象的属性值精准拷贝,如果属性值是基础数据类型,拷贝的就是基础数据类型的值,如果属性值是引用数据类型,拷贝的是内存地址。
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。也就是说是在堆内存中重新开辟空间,拷贝后数据存放在新的地址,同时指针指向新的地址,与原数据完全隔离。
常见的浅拷贝的实现方式有:
object.assign(target, …sources)
扩展运算符 …
concat 拷贝数组
slice 拷贝数组 arr.slice(begin, end);
// Object.assign()
let target = { a: 1 }
let source = { b: 2, c: { d: 3 } };Object.assign(target, source);
console.log(target); // { a: 1, b: 2, c: { d: 3 } };target.b = 5;
target.c.d = 4;
console.log(source); // { b: 2, c: { d: 4 } };
console.log(target); // { a: 1, b: 5, c: { d: 4 } };// ...
let obj = { a:1, b: { c:1 } }
let obj2 = { ...obj }
let arr = [1, 2, 3];
let newArr = [...arr];// concat()
let arr = [1, 2, 3];
let newArr = arr.concat();// slice()
let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
但是使用 object.assign 方法有几点需要注意:
- 它不会拷贝对象的继承属性;
- 它不会拷贝对象的不可枚举的属性;
- 可以拷贝 Symbol 类型的属性。
let obj1 = { a:{ b:1 }, sym:Symbol(1)};
Object.defineProperty(obj1, 'innumerable' ,{value:'不可枚举属性',enumerable:false
});
let obj2 = {};
Object.assign(obj2,obj1)
obj1.a.b = 2;
console.log('obj1',obj1); // { a: { b: 2 }, sym: Symbol(1), innumerable: '不可枚举属性'}
console.log('obj2',obj2); // { a: { b: 2 }, sym: Symbol(1)}
深拷贝的实现方式:JSON.stringify
但是使用 JSON.stringify 实现深拷贝还是有一些地方值得注意,总结下来主要有这几点:
- 拷贝的对象的值中如果有函数、undefined、symbol 这几种类型,经过 JSON.stringify 序列化之后的字符串中这个键值对会消失;
- 拷贝 Date 引用类型会变成字符串;
- 无法拷贝不可枚举的属性;
- 无法拷贝对象的原型链;
- 拷贝 RegExp 引用类型会变成空对象;
- 对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null;
- 无法拷贝对象的循环应用,即对象成环 (obj[key] = obj)。
function Obj() { this.func = function () { alert(1) }; this.obj = {a:1};this.arr = [1,2,3];this.und = undefined; this.reg = /123/; this.date = new Date(0); this.NaN = NaN;this.infinity = Infinity;this.sym = Symbol(1);
}
let obj1 = new Obj();
Object.defineProperty(obj1,'innumerable',{ enumerable:false,value:'innumerable'
});
console.log('obj1',obj1);
let str = JSON.stringify(obj1);
let obj2 = JSON.parse(str);
console.log('obj2',obj2);
手写浅拷贝
const shallowClone = (target) => {if (typeof target === 'object' && target !== null) {let cloneTarget = Array.isArray(target) ? [] : {};for(let prop in target) {if (target.hasOwnProperty(prop)) { // 遍历对象自身可枚举属性(不考虑继承属性和原型对象cloneTarget[prop] = target[prop]}}return cloneTarget} else {return target}
}
手写深拷贝
const deepClone = (target) => {if (typeof target === 'object' && target !== null) {let cloneTarget = Array.isArray(target) ? [] : {};for(let prop in target) {if (target.hasOwnProperty(prop)) { // 遍历对象自身可枚举属性(不考虑继承属性和原型对象cloneTarget[prop] = deepClone(target[prop]); //递归导致日期、正则变成{}}}return cloneTarget} else {return target}
}
改进版深拷贝
考虑日期、正则、循环引用
const deepClone = (target, map = new WeakMap()) => {if (target === null) return nullif (typeof target !== 'object' || target.constructor === Date || target.constructor === RegExp) return targetif (map.has(target)) return target // 解决循环引用const deepTarget = Array.isArray(target) ? [] : {};map.set(target, true)for (let prop in target) {if (target.hasOwnProperty(prop)) { // 遍历对象自身可枚举属性(不考虑继承属性和原型对象deepTarget[prop] = deepClone(target[prop], map)}}return deepTarget
}
4. 原型
原型对象
首先思考三个问题:
- 什么是原型对象?
- 原型对象何时产生?
- 原型对象如何访问?
什么是原型对象?
原型对象本质就是一个对象,所有函数都有prototype属性,该属性指向函数的原型对象,原型对象中包括:
- constructor: 指向构造函数
- 继承自Object的属性和方法
原型对象何时产生?
原型对象在函数创建的时候产生,每声明一个函数,都会做以下操作:
- 浏览器会在内存中创建一个对象
- 对象中添加一个constructor属性
- constructor属性指向该函数
- 将新创建的对象赋值给函数的prototype属性
原型对象如何访问?
函数名.prototype
通过函数实例的__proto__属性,函数每创建一个实例,该实例内部包含一个指针,指向构造函数的原型对象。
5. 原型链
原型链就是访问实例上某个属性或者方法时,先在实例中查找,找到即返回;若没有,则通过__proto__属性到构造函数的原型对象prototype上去找,找到则返回;若没有,则继续到构造函数的原型对象prototype的__proto__属性查找,直到搜索到Object.prototype为止。
一张图理解原型、原型链
总结
所有函数都是Function的实例,Function也是Function的实例
Function继承Object,除Object外,其他一切皆继承自Object
6. 继承
有两个函数、函数A、函数B,实现函数A继承函数B的属性和方法:
原型链继承
A.prototype = new B()
A.prototype.constructor = Avar a1 = new A()
var a2 = new A()
缺点:
- 父类B函数原型对象的引用类型属性会被实例 a1 a2共享
- 创建子类A的实例无法向父类B传参数
- 子类无法实现继承多个函数,这里A只能继承自B
构造函数继承
function A(e) {B.call(this, e)
}
缺点:
子类无法访问父类原型上的属性和方法
组合继承
function B (name, age) {this.name = namethis.age = age
}
B.prototype.setName = function (name) {this.name = name
}function A (name, age, price) {B.call(this, name, age)this.price = price
}A.prototype = new B ()
A.prototype.constructor = AA.prototype.setPrice = function (price) {this.price = price
}var sub = new A()
缺点:
调用了两次B():一次是B.call();一次是new B()
寄生组合继承
Object.create 方法,这个方法接收两个参数:一是用作新对象原型的对象、二是为新对象定义额外属性的对象(可选参数)。
function B (name, age) {this.name = namethis.age = age
}
B.prototype.setName = function (name) {this.name = name
}
t
function A (name, age, price) {B.call(this, name, age)this.price = price
}// 第一种写法
// 创建一个对象{},并且把对象的_proto_赋值为Object.create 的参数
// A.prototype.__proto__ = B.prototype
A.prototype = Object.create(B.prototype, {consturctor: A})// 第二种写法
//var F = function () { } //核心代码
//F.prototype = B.prototype; //核心代码
//A.prototype = new F();
//A.prototype.contructor = AA.prototype.setPrice = function (price) {this.price = price
}var sub = new A()
ES6 Class类
class B {static mood = 'good' // 静态属性constructor () {this.money = 1000000}buybuybuy () {this.money -= 100console.log('money', this.money)}
}class A extends B {super()
} var a1 = new A()
a1.buybuybuy()
7. 执行上下文(Execution Context)
浏览器获取到源代码后,主要做了几个事情:
- 分词/词法分析():将代码进行分割,生成token;
- 解析/语法分析():按照语法将token转换成AST抽象语法树;
- 可执行代码:解析器生成字节码,逐行解释执行,分析器监控热点代码,编译器将热点代码编译为机器码。
什么是执行上下文?
执行上下文,又称执行上下文环境。执行上下文分为三种类型:
- 全局执行上下文:程序开始时,会创建全局执行上下文,并压入执行栈中。
- 函数执行上下文:当函数被调用时创建函数执行上下文,并将函数压入执行栈中。
- eval执行上下文:eval函数专有的执行上下文。
执行上下文分两个阶段:创建阶段和执行阶段。
创建阶段
执行上下文主要由两部分组成:词法环境和变量环境。
词法环境(LexicalEnvironment)
词法环境分类:全局、函数、模块
词法环境构成:
环境记录(Environment Record):存放、初始化变量
声明式环境记录(Declarative Environment Record): 存放直接用标识符定义的元素,比如const let声明的变量对象式环境记录(Object Environment Record):主要用于with的语法环境。
外部环境(Outer):创建作用域链,访问父词法作用域的引用
thisBinding:确定当前环境中this的指向
变量环境(variableEnvironment)
也是一个词法环境。主要的区别在于通过var 声明的变量以及函数声明存放在变量环境。
简单来说,执行上下文创建阶段主要做了三件事情:
- 初始化变量、函数、形参
- 创建作用域链
- 绑定this
executionContext = {variableObject: {arguments: {},name: undefined,getData: undefined}, // 初始化变量、函数、形参scopeChain: {}, // 创建作用域链this: {} // 绑定this
}
执行阶段
执行阶段主要做了两件事:
- 分配变量、函数的引用、赋值
- 执行代码
变量提升 vs 暂时性死区
var声明的变量以及函数声明,在执行上下文创建阶段,已经初始化完成,并赋值为undefined,代码未执行到var赋值行,也可以访问var定义的变量,值为undefined,这种现象被称作变量提升
相反,由const、let声明的变量,在词法环境中,初始化时会被置为标志位,在代码没执行到let、const赋值行时,提前读取变量会报错,这个特性叫做暂时性死区。
执行上下文栈
浏览器的JS解释器是单线程的,相当于浏览器在同一时间只能做一件事。
代码中只有一个全局执行上下文,和无数个函数执行上下文,组成了执行上下文栈。
一个函数的执行上下文,在函数执行完毕后会被移除执行栈。
8. 作用域
作用域的主要用途是隔离变量和函数,并控制它们的生命周期。主要分为三种类型:
- 全局作用域
- 函数作用域
- 块级作用域
作用域是在执行上下文创建时定义的,不是在代码执行时创建的,因此又称为词法作用域。
词法作用域 vs 动态作用域
词法作用域语动态作用域的区别是,词法作用域是执行上下文创建阶段阶段就定义的,动态作用域是指代码执行阶段创建的。
为了更好的理解JS采用的是词法作用域,看一下例子:
var name = 'xuna'
function getName() {console.log(name)
}
function getName1() {var name = 'na.xu'return getName()
}
getName1() // xuna
作用域链
当一个函数嵌套另一个函数时,在当前执行上下文环境的词法环境和变量环境的环境记录(Environment Record)中无法找到某个变量,就会通过外部环境(Outer)去访问父词法作用域,如果还没找到,就一层一层向上寻找,直到找到该变量或抵达全局作用域为止,这样的链式关系称为作用域链。
9. 闭包
闭包一般发生在函数嵌套时,内部函数访问外部函数的变量。
高级程序设计三中:闭包是指有权访问另一个函数作用域中的变量的函数,可以理解为(能够读取其他函数内部变量的函数)
再理解一下:
一个函数执行完,被弹出执行栈,当前执行上下文中不能直接访问被弹出栈的函数的词法作用域,而另一个函数中还保留了对该函数词法作用域的引用,这个引用就是闭包。
闭包的应用
封装私有变量
function Person() {var money = 10000return buy() {money -= 1}
}var person = new Person()
person.buy() // money为person的私有变量,只能通过buy()修改
缓存数据
function getDataList() {let data = nullreturn {getData() {if(data) return Promise.resolve(data)return fetch().then(res => data = res.json())}}
}const list = getDataList()
list.getData()
柯里化
JS内功修炼-基础篇相关推荐
- 开发内功修炼网络篇电子书出炉!!!
点击上方蓝字"开发内功修炼",关注并设为星标 了解你的每一比特,用好你的每一纳秒 飞哥的开发内功修炼技术号更新文章有一年多了,以前的文章都是单独介绍一个技术点,没给大家一个整体的视 ...
- Three.js(2)--->基础篇-Helpers(辅助对象/辅助线)
在Three.js中有许多的Helper(辅助对象)用来帮助我们的开发. 本篇例举几个常见的,方便理解.以及一些效果 文章目录 前言 一.AxesHelper 二.BoxHelper 三.Box3He ...
- 【知识点总结】内功修炼-算法篇
[知识点总结]内功修炼-算法篇 五大基本特征 设计要求 排序算法 1.选择排序 2.冒泡排序 3.插入排序 时间复杂度总汇 排序算法 概念 分治思想 递归 知识点 前缀表达式 中缀表达式 后缀表达式 ...
- Node.js后端开发 - 基础篇 #18 nodemon工具
文章目录 前言 nodemon工具简单介绍 nodemon工具安装 nodemon工具使用(node app.nodemon app) nodemon工具使用(npm run start) 前言 上篇 ...
- 开发内功修炼内存篇汇总
最近终于抽空把内存篇更新完了,我分享出来的这些问题,其实都是我之前没有搞的特别明白的困惑.刚刚开始的时候,可能就是在我脑子里蹦出的一个疑问,比如内存随机IO会比顺序IO慢吗? 为了解开这个疑惑,我曾经 ...
- 【android免root脚本制作】总览Auto.js开发小结——基础篇
Auto.js是什么 Auto.js是一款写脚本,ui界面,运行脚本,制作简单安卓app的一体式软件.并且是全开源的免费APP,类似于按键精灵,而且本软件有全部按键精灵的功能,还有其他例如控件操作等, ...
- JS服务器端开发基础篇(Array.slice方法和splice方法)
Array.slice方法和splice方法在众多的JS数组中属于比较复杂的一个方法,而且容易记混.搜索网络上很多资料都没有发现系统的总结.特别归纳如下,不完全处还希望各位批评指正. 一.slice ...
- js页面排序-----基础篇
由于客户查询出来的结果集比较多,一页就有五百条,所以客户希望单行表格的列头的时候可以根据这一页在本页排序,既然是只涉及到页面的排序,则不需去和服务器交互,直接利用js把结果集表格重排一下即可. 在网上 ...
- 3.3.1JavaScript网页编程——WebAPI(JS之DOM基础篇,含事件)
目录 DOM和BOM DOM 根据CSS选择器来获取DOM元素 (重点) 选择引号里面加**css选择器** 获取页面标签querySelect和querySelectAll 其他获取DOM元素方法( ...
- JS基础篇--HTML DOM classList 属性
页面DOM里的每个节点上都有一个classList对象,程序员可以使用里面的方法新增.删除.修改节点上的CSS类.使用classList,程序员还可以用它来判断某个节点是否被赋予了某个CSS类. 添加 ...
最新文章
- atitit.人脸识别的应用场景and使用最佳实践 java .net php
- ElasticSearch和mongodb的对比
- shell date cal
- 信息论基础(学习笔记整理)
- 基于抛物线过渡(梯形加减速)的空间直线插补算法与空间圆弧插补算法(Matlab)
- java 支付宝转账_Java 支付宝支付,退款,单笔转账到支付宝账户(单笔转账到支付宝账户)...
- html画布刮刮乐,h5canvas实现刮刮乐效果的方法
- wps怎么把xlsx转成html,怎样把wps转换成excel
- vuejs中使用vuex的两种方案之一
- 用python画微笑脸表情_“裂开了,苦涩了,翻白眼”!我用Python画出微信新出的表情包...
- python 矩阵和三角函数
- flask android app socketio加解密 匿名加密聊天室 不被任何官方非官方机构个人监视的匿名聊天室!!! 想聊什么就聊什么!
- 12、乐趣国学—践行《弟子规》的“信”懂得处世之道(下篇)
- 阐述HTML语言的基本语法规则,信息组织学》考试试卷(A)试卷(一)
- 【附源码例】快捷指令实现调出iOS隐藏应用程序-原理解析
- html标题如何设置行书,四招打通行书之“气”
- 计网课设 模拟实验拓扑
- 定理在数学中的简写形式_数学中的s代表着什么? s符号在数学中表示什么
- 管理经济学【八】之 完全竞争市场中的企业决策
- 在WSL中使用Linux桌面环境的尝试与总结