从JS的运行
,设计
,数据
,应用
四个角度来梳理JS核心的知识点数组
JS运行浏览器
JS设计服务器
JS数据markdown
JS应用闭包
大概分为四个阶段app
词法分析:将js代码中的字符串分割为有意义的代码块,称为词法单元
异步
var a = 1
语法分析:将词法单元
流转换成一颗 抽象语法树(AST)
,并对生成的AST树节点进行处理,函数
为何须要抽象语法树呢?oop
预解析阶段:性能
执行阶段:
指定了函数和变量的做用范围
全局做用域
和函数做用域
,全局变量和函数声明会提高
function foo() {}
var foo = function () {}
var foo = new Function()
直接建立
和变量赋值
变量赋值函数
和赋值普通变量
的优先级按位置来,变量名相同前者被覆盖有不一样的做用域,就有不一样的执行环境,咱们须要来管理这些上下文的变量
执行上下文栈
来维护,后进先出变量环境
和词法环境
变量环境
里就包含着当前环境里可以使用的变量做用域链
词法做用域
决定的静态做用域
,由函数声明的位置决定,和函数在哪调用无关,也就js这么特殊this也是!
)var a = 2;
function foo() {
console.log(a); // 静态2 动态3
}
function bar() {
var a = 3;
foo();
}
bar();
复制代码
做用域
的限制,咱们没法在函数做用域外部访问到函数内部定义的变量,而实际需求须要,这里就用到了闭包
1. 闭包做用
for循环遍历进行事件绑定
输出i值时为for循环的长度+1for(var i = 0;i < 3;i++){
document.getElementById(`item${i+1}`).onclick = function() {
console.log(i);//3,3,3
}
}
复制代码
闭包
Closure
的对象,用来存储闭包变量for(var i = 0;i < 3;i++){
document.getElementById(`item${i+1}`).onclick = make(i);
}
function make(e) {
return function() {
console.log(e)//0,1,2
};
复制代码
闭包注意
因为疏忽或错误形成程序未能释放已经再也不使用的内存
,就形成了内存泄漏
对象
、构造函数
、new
操做符理念,而抛弃掉了了复杂的class
(类)概念继承
的机制,来把全部对象联系起来,就可使用构造函数没法共享属性和方法
prototype
属性prototype
属性,就是咱们常说的原型prototype
属性,实例对象须要共享的方法,都放在此对象上,整个核心设计完成后,后面的API也就瓜熟蒂落
原型
proto
Object.getPrototype()
方法获取原型constructor 原型没有指向实例,由于一个构造函数能够有多个对象实例 可是原型指向构造函数是有的,每一个原型都有一个constructor属性指向关联的构造函数
function Per() {} // 构造函数
const chi = new Per() // 实例对象
chi.__proto__ === Per.prototype // 获取对象的原型 也是就构造函数的prototype属性
Per.prototype.constructor === Per // constructor属性 获取当前原型关联的构造函数
复制代码
实例与原型
原型链
function Foo() {}
Foo.prototype.name = 'tom'
const foo = new Foo()
foo.name = 'Jerry'
console.log(foo.name); // Jerry
delete foo.name
console.log(foo.name); // tom
复制代码
首先亮出你们熟悉的网图
就是实例与构造函数,原型之间的链条关系
实例的 proto 指向 原型
构造函数的 prototype 属性 指向 原型
原型的 constructor 属性 指向 构造函数
全部构造函数的 proto 指向 Function.prototype
Function.prototype proto 指向 Object.prototype
Object.prototype proto 指向 null
函数对象原型(Function.prototype) 是负责造构造函数的机器,包含Object、String、Number、Boolean、Array,Function。 再由构造函数去制造具体的实例对象
function Foo() {}
// 1. 全部构造函数的 __proto__ 指向 Function.prototype
Foo.__proto__ // ƒ () { [native code] }
Function.__proto__ // ƒ () { [native code] }
Object.__proto__ // ƒ () { [native code] }
// 2. 全部构造函数原型和new Object创造出的实例 __proto__ 指向 Object.prototype
var o = new Object()
o.__proto__ // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ ...}
Function.prototype.__proto__ // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ ...}
// 3. Object.prototype 指向null
Object.prototype.__proto__ // null
复制代码
obj.b();
// 指向obj`var b = obj.b; b();
// 指向全局window(函数方法其实就是window对象的方法,因此与上同理,谁调用就指向谁)var b = new Per();
// this指向当前实例对象obj.b.apply(object, []);
// this指向当前指定的值谁调用就指向谁
const obj = {a: 1, f: function() {console.log(this, this.a)}}
obj.f(); // 1
const a = 2;
const win = obj.f;
win(); // 2
function Person() {
this.a = 3;
this.f = obj.f;
}
const per = new Person()
per.f() // 3
const app = { a: 4 }
obj.f.apply(app); // 4
复制代码
this
指向是动态做用域
,谁调用指向谁,
var obj = {
value: 1,
}
function foo (name, old) {
return {
value:this.value,
name,
old
}
}
foo.call(obj, 'tom', 12); // {value: 1, name: "tom", old: 12}
复制代码
Function.prototype.call1 = function (context, ...args) {
if(typeof this !== 'function') {throw new Error('Error')} // 非函数调用判断处理
context = context || window; // 非运算符断定传入参数 null则指向window
const key = Symbol(); // 利用symbol建立惟一的属性名,防止覆盖原有属性
context[key] = this; // 把函数做为对象的属性,函数的this就指向了对象
const result = context[key](...args) // 临时变量赋值为执行对象方法
delete context[key]; // 删除方法,防止污染对象
return result // return 临时变量
}
复制代码
改变this指向,大部分是为了借用方法或属性
Object
的toString
方法 Object.prorotype.toString.call()
function Chi() {Par.call(this)}
var obj = {
value: 1,
}
function foo (name, old) {
return {
value:this.value,
name,
old
}
}
foo.apply(obj, ['tom', 12], 24); // {value: 1, name: "tom", old: 12}
复制代码
Function.prototype.apply1 = function (context, args) { // 与call实现的惟一区别,此处不用解构
if(typeof this !== 'function') {throw new Error('Error')} // 非函数调用判断处理
context = context || window; // 非运算符断定传入参数 null则指向window
const key = Symbol(); // 利用symbol建立惟一的属性名,防止覆盖原有属性
context[key] = this; // 把函数做为对象的属性,函数的this就指向了对象
const result = context[key](...args) // 临时变量赋值为执行对象方法
delete context[key]; // 删除方法,防止污染对象
return result // return 临时变量
}
复制代码
const obj = {
value: 1
};
function foo(name, old) {
return {
value: this.value,
name,
old
}
}
const bindFoo = foo.bind(obj, 'tom');
bindFoo(12); // {value: 1, name: "tom", old: 12}
复制代码
Function.prototype.bind1 = function (context, ...args) {
if(typeof this !== 'function') {throw new Error('Error')} // 非函数调用判断处理
const self = this; // 保存当前执行环境的this
const Foo = function() {} // 保存原函数原型
const res = function (...args2) { // 建立一个函数并返回
return self.call( // 函数内返回
this instanceof res ? this : context, // 做为构造函数时,this指向实例,:做为普通函数this正常指向传入的context
...args, ...args2) // 两次传入的参数都做为参数返回
}
Foo.prototype = this.prototype; // 利用空函数中转,保证在新函数原型改变时bind函数原型不被污染
res.prorotype = new Foo();
return res;
}
复制代码
看看new
出的实例能作什么
function Persion(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
console.log(this.name)
}
var per = new Person('tom', 10)
per.name // 10 可访问构造函数的属性
per.sayName // tom 访问prototype的属性
复制代码
new实现
var person = factory(Foo)
function factory() { // new是关键字,没法覆盖,函数替代
var obj = {}; // 新建对象obj
var con = [].shift.call(arguments); // 取出构造函数
obj._proto_ = con.prototype; // obj原型指向构造函数原型
var res = con.apply(obj, arguments); // 构造函数this指向obj
return typeof(res) === 'object' ? ret : obj;
}
复制代码
寄生组合继承实现
function P (name) { this.name = name; } // 父类上绑定属性动态传参
P.prototype.sayName = function() { // 父类原型上绑定方法
console.log(111)
}
function F(name) { // 子类函数里,父类函数利用`call`函数this指向子类,传参并执行
P.call(this, name)
}
const p = Object.create(P.prototype) // Object.create 不会继承构造函数多余的属性和方法
p.constructor = F; // constructor属性丢失,从新指向
F.prototype = p; // 子类原型 指向 中转对象
const c = new F('tom'); // 子类实例 化
c.name // tom
c.sayName() // 111
复制代码
JS分为基本类型和引用类型
Boolean
Undefined
String
Number
Null
Symbol
Object
Funciton
Array
等js数据类型中分为基本类型,和引用类型
两个对象 指向了同一地址 修改其中一个就会影响另外一个
特殊的数组对象方法(深拷贝一层)
obj2 = Object.assign({}, obj1)
arr2 = [].concat(arr1)
arr2 = arr1.slice(0)
arr2 = Array.form(arr1)
arr2 = [...arr1];
以上方法都只能深拷贝一层
JSON.parse(JSON.stringify(obj))(多层) 不拷贝一个对象,而是拷贝一个字符串,会开辟一个新的内存地址,切断了引用对象的指针联系
缺点
手动实现深拷贝(多层)
function Judgetype(e) {
return Object.prototype.toString.call(e).slice(8, -1).toLowerCase();
}
function Loop(param) {
let target = null;
if(Judgetype(param) === 'array') {
target = [];
for(let key of param.keys()){
target[key] = Deep(param[key]);
}
} else {
target = {};
Object.keys(obj).forEach((val) => {
target[key] = Deep(param[key]);
})
}
return target;
}
function Deep(param) {
//基本数据类型
if(param === null || (typeof param !== 'object' && typeof param !== 'function')) {
return param;
}
//函数
if(typeof param === 'function') {
return new Function('return ' + param.toString())();
}
return Loop(param);
}
复制代码
typeof
没法区分 object, null 和 array
typeof(1) // number
typeof('tom') // string
typeof(undefined) // undefined
typeof(null) // object
typeof(true) // boolean
typeof(Symbol(1)) // symbol
typeof({a: 1}) // object
typeof(function () {}) // function
typeof([]) // object
复制代码
instanceof
[] instanceof Array; // true
{} instanceof Object;// true
var a = function (){}
a instanceof Function // true
复制代码
Object.prototype.toString.call
目前最优方案
toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型。
对于 Object 对象,直接调用 toString() 就能返回 [object Object] 。而对于其余对象,则须要经过 call / apply 来调用才能返回正确的类型信息
这里就是call的应用场景,巧妙利用call借用Object的方法实现类型的判断
function T(e) {
return Object.prototype.toString.call(e).slice(8, -1).toLowerCase();
}
T(1) // number
T('a') // string
T(null) // null
T(undefined) // undefined
T(true) // boolean
T(Symbol(1)) // symbol
T({a: 1}) // object
T(function() {}) // function
T([]) // array
T(new Date()) // date
T(/at/) // RegExp
复制代码
==
非严格比较 容许 类型转换===
严格比较 不容许 类型转换引用数据类型栈中存放地址,堆中存放内容,即便内容相同 ,但地址不一样,因此二者仍是不等的
const a = {c: 1}
const b = {c: 1}
a === b // false
复制代码
==
可能会有类型转换,不只在相等比较上,在作运算的时候也会产生类型转换,这就是咱们说的隐式转换布尔比较,先转数字
true == 2 // false
||
1 == 2
// if(X)
var X = 10
if(X) // true
10 ==> true
// if(X == true)
if(X == true) // false
10 == true
||
10 == 1
复制代码
数字和字符串作比较,字符串
转数字
0 == '' // true
||
0 == 0
1 == '1' // true
||
1 == 1
复制代码
对象类型和原始类型的相等比较
[2] == 2 // true
|| valueOf() // 调用valueOf() 取自身值
[2] == 2
|| toString() // 调用toString() 转字符串
"2" == 2
|| Number() // // 数字和字符串作比较,`字符串`转`数字`
2 == 2
复制代码
小结
js使用某些操做符会致使类型变换, 常见是+,==
字符串
转数字
课外小题 实现 a == 1 && a == 2 && a == 3
const a = {
i: 1,
toString: function () {
return a.i++;
}
}
a == 1 && a == 2 && a == 3
复制代码
===
判断时二者仍是不等的// 判断二者非对象返回
// 判断长度是否一致
// 判断key值是否相同
// 判断相应的key值里的对应的值是否相同
这里仅仅考虑 对象的值为object,array,number,undefined,null,string,boolean
关于一些特殊类型 `function date RegExp` 暂不考虑
function Judgetype(e) {
return Object.prototype.toString.call(e).slice(8, -1).toLowerCase();
}
function Diff(s1, s2) {
const j1 = Judgetype(s1);
const j2 = Judgetype(s2);
if(j1 !== j2){
return false;
}
if(j1 === 'object') {
if(Object.keys(s1).length !== Object.keys(s2).length){
return false;
}
s1[Symbol.iterator] = function* (){
let keys = Object.keys( this )
for(let i = 0, l = keys.length; i < l; i++){
yield {
key: keys[i],
value: this[keys[i]]
};
}
}
for(let {key, value} of s1){
if(!Diff(s1[key], s2[key])) {
return false
}
}
return true
} else if(j1 === 'array') {
if(s1.length !== s2.length) {
return false
}
for(let key of s1.keys()){
if(!Diff(s1[key], s2[key])) {
return false
}
}
return true
} else return s1 === s2
}
Diff( {a: 1, b: 2}, {a: 1, b: 3}) // false
Diff( {a: 1, b: [1,2]}, {a: 1, b: [1,3]}) // false
复制代码
其实对象遍历 return 也能够用for in, 关于遍历原型的反作用能够用hasOwnproperty判断去弥补
for(var key in s1) {
if(!s1.hasOwnProperty(key)) {
if(!Diff(s1[key], s2[key])) {
return false
}
}
}
复制代码
`最普通 for循环` // 较为麻烦
for(let i = 0,len = arr.length; i < len; i++) {
console.log(i, arr[i]);
}
`forEach` 没法 break return
`for in` 不适合遍历数组,
`for...in` 语句在w3c定义用于遍历数组或者对象的属性
1. index索引为字符串型数字,不能直接进行几何运算
2. 遍历顺序有可能不是按照实际数组的内部顺序
3. 使用for in会遍历数组全部的可枚举属性,包括原型
`for of` 没法获取下标
// for of 兼容1
for(let [index,elem] of new Map( arr.map( ( item, i ) => [ i, item ] ) )){
console.log(index);
console.log(elem);
}
// for of 兼容2
let arr = [1,2,3,4,5];
for(let key of arr.keys()){
console.log(key, arr[key]);
}
for(let val of arr.values()){
console.log(val);
}
for(let [key, val] of arr.entries()){
console.log(key, val);
}
2.
复制代码
缺点:会遍历出对象的全部可枚举的属性, 好比prototype上的
var obj = {a:1, b: 2}
obj.__proto__.c = 3;
Object.prototype.d = 4
for(let val in obj) {
console.log(val) // a,b,c,d
}
// 优化
for(let val in obj) {
if(obj.hasOwnProperty(val)) { // 判断属性是存在于当前对象实例自己,而非原型上
console.log(val) // a,b
}
}
复制代码
var obj = { a:1, b: 2 }
Object.keys(obj).forEach((val) => {
console.log(val, obj[val]);
// a 1
// b 2
})
复制代码
var obj = { a:1, b: 2 }
obj[Symbol.iterator] = function* (){
let keys = Object.keys( this )
for(let i = 0, l = keys.length; i < l; i++){
yield {
key: keys[i],
value: this[keys[i]]
};
}
}
for(let {key, value} of obj){
console.log( key, value );
// a 1
// b 2
}
复制代码
0.1 + 0.2 = 0.30000000000000004
function getLen(n) {
const str = n + '';
const s1 = str.indexOf('.')
if(s1) {
return str.length - s1 - 1
} else {
return 0
}
}
function add(n1, n2) {
const s1 = getLen(n1)
const s2 = getLen(n2)
const max = Math.max(s1, s2)
return (n1 * Math.pow(10, max) + n2 * Math.pow(10, max)) / Math.pow(10, max)
}
add(11.2, 2.11) // 13.31
复制代码
function add(a, b) {
let i = a.length - 1;
let j = b.length - 1;
let carry = 0;
let ret = '';
while(i>=0|| j>=0) {
let x = 0;
let y = 0;
let sum;
if(i >= 0) {
x = a[i] - '0';
i--
}
if(j >=0) {
y = b[j] - '0';
j--;
}
sum = x + y + carry;
if(sum >= 10) {
carry = 1;
sum -= 10;
} else {
carry = 0
}
ret = sum + ret;
}
if(carry) {
ret = carry + ret;
}
return ret;
}
add('999999999999999999999999999999999999999999999999999999999999999', '1')
// 1000000000000000000000000000000000000000000000000000000000000000
复制代码
场景:
定义:
实现思想:
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
复制代码
场景:
定义:
function foo(func, wait) {
var context, args;
var flag = 0;
return function () {
var now = +new Date();
context = this;
args = arguments;
if(now - flag > 0) {
func.apply(context, args);
flag = now;
}
}
}
复制代码
function foo(func, wait) {
var context, args;
var timeout;
return function() {
if(!timeout){
setTimeout(()=>{
timeout = null;
func.apply(context, args);
}, wait)
}
}
}
复制代码
参数复用
,提早返回
,延迟执行
function add(...args) {
var fn = function(...args1) {
return add.apply(this, [...args, ...args1]);
}
fn.toString = function() {
return args.reduce(function(a, b) {
return a + b;
})
}
return fn;
}
add(1)(2)(3).toString(); // 6
复制代码