本篇文章记录本身对es5知识点的理解javascript
======================相关文章和开源库=======================前端
系列文章vue
1.前端知识梳理-HTML,CSS篇
java
2.前端知识梳理-ES5篇
面试
3.前端知识梳理-ES6篇
数组
4.前端知识梳理-VUE篇
缓存
我的维护的开源组件库bash
2.树形组织结构组件
app
3.bin-admin ,基于bin-ui的后台管理系统集成方案
4.bin-data ,基于bin-ui和echarts的数据可视化框架
5.其他生态连接
========================================================
es5有三种方式建立对象,分别是
// 第一种方式,字面量var
o1 = {name: "o1"}var
o2 = new Object({name: "o2"})// 第二种方式,经过构造函数var
M = function(name){ this.name = name }var
o3 = new M("o3")// 第三种方式,Object.createvar p = {name: "p"}var
o4 = Object.create(p) 对于对象,在这里也不作赘述,这里解释一下js中什么是标识符复制代码
/*
标识符
- 在JS中全部的能够由咱们自主命名的均可以称为是标识符
- 例如:变量名、函数名、属性名都属于标识符
- 命名一个标识符时须要遵照以下的规则:
1.标识符中能够含有字母 、数字 、下划线_ 、$符号
2.标识符不能以数字开头
3.标识符不能是ES中的关键字或保留字
4.标识符通常都采用驼峰命名法
- JS底层保存标识符时其实是采用的Unicode编码,
因此理论上讲,全部的utf-8中含有的内容均可以做为标识符
*/
这里就看出js的缺陷了,若是对象中的属性不符合标识符规范怎么办,也就是我用.操做符没法获取属性的时候,好比说 var obj={ 1 : 1 } console.log(obj.1)
事实上咱们是没法经过点操做符来获取这个属性的,这里咱们只能用var obj={ ‘1’:1 }
来声明对象,那该如何获取属性呢,js为咱们提供了一个相似数组索引 [ ] 的方式来获取对象
如上面的 var obj={name:’张三’}
咱们能够用obj.name 或者 obj[‘name’] 这两种方式来获取 可是var obj={ ‘1’:1 } 只能用obj[‘1’]来获取这个属性值
那这两种有什区别呢
. 运算符:右侧必须是一个属性名称命名的简单标识符,如.name
[]:右侧必须是一个计算结果为字符串的表达式
那既然[]是一个计算结果为字符串的表达式,那就给[]赋予了强大的功能,如字符串拼接,三目运算符等
let obj={'1':123, name:'张三'}
console.log(obj['1'],obj.name,obj['name']) // 输出 123 "张三" "张三"复制代码
不一样于Java中的比较,js有这两种比较的方式,但他们有什么区别呢
简单来讲: == 表明相同, ===表明严格相同, 为啥这么说呢
这么理解: 当进行双等号比较时候: 先检查两个操做数数据类型,若是相同, 则进行===比较, 若是不一样, 则愿意为你进行一次类型转换, 转换成相同类型后再进行比较, 而===比较时, 若是类型不一样,直接就是false.
比较过程:
双等号==:
(1)若是两个值类型相同,再进行三个等号(===)的比较
(2)若是两个值类型不一样,也有可能相等,需根据如下规则进行类型转换在比较:
1)若是一个是null,一个是undefined,那么相等
2)若是一个是字符串,一个是数值,把字符串转换成数值以后再进行比较
三等号===:
(1)若是类型不一样,就必定不相等
(2)若是两个都是数值,而且是同一个值,那么相等;若是其中至少一个是NaN,那么不相等。(判断一个值是不是NaN,只能使用isNaN( ) 来判断)
(3)若是两个都是字符串,每一个位置的字符都同样,那么相等,不然不相等。
(4)若是两个值都是true,或是false,那么相等
(5)若是两个值都引用同一个对象或是函数,那么相等,不然不相等
(6)若是两个值都是null,或是undefined,那么相等
那何时用==何时用===呢,我我的认为的是
只有在判断obj.a==null的时候使用双等,等同于obj.a===undefind||obj.a===null
其余一概使用===(三等号)由于咱们一般在判断是否相等的时候首先就得肯定两遍的类型
先来看一个现象,那么为何会出现这种状况呢
由于 JS 采用 IEEE 754 双精度版本(64位),而且只要采用 IEEE 754 的语言都有该问题。
咱们都知道计算机表示十进制是采用二进制表示的,因此 0.1 在二进制表示为
// (0011) 表示循环
0.1 = 2^-4 * 1.10011(0011)
这个问题简单来讲是底层在表示0.1和0.2的时候采用的是二进制,在采用这个双精度版本时,二进制相加后再转换为十进制的时候就会获得结果是不等于0.3的
那么咱们实际编码时若是遇到这种状况该怎么判断呢
原生的解决办法以下
parseFloat((0.1 + 0.2).toFixed(10))或者转换成整数计算后再转换成小数
每一个函数都有 prototype
属性,除了 Function.prototype.bind()
,该属性指向原型。
每一个对象都有 __proto__
属性,指向了建立该对象的构造函数的原型。其实这个属性指向了 [[prototype]]
,可是 [[prototype]]
是内部属性,咱们并不能访问到,因此使用 _proto_ 来访问。
对象能够经过 __proto__
来寻找不属于该对象的属性,__proto__
将对象链接起来组成了原型链。
构造函数
function Foo(name,age){
this.name = name;
this.age = age;
//return this;默认有这一行
}
var f = new Foo('zhangshan',20);
//1.建立了一个新对象
//2.将this指向这个新对象
//3.执行构造函数里面的代码
//4.返回新对象(this)
var a={} //实际上是var a = new Object()语法糖
var a=[] //实际上是var a = new Array()语法糖
function Foo(){} // 实际上是var Foo = new Function()
// 使用instanceof判断一个函数是不是一个变量的构造函数
// f instanceof Foo ==> true
// f.__proto__一层层往上寻找可否查询到Foo.prototype
// f instanceof Object ==> true复制代码
原型的规则和示例
// 1.全部的引用类型(数组,对象,函数),都具备对象特性,便可以自由扩展属性(null除外)
var obj={};obj.a=100;
var arr={};arr.a=100;
function fn(){}
fn.a=100;
// 2.全部的引用类型(数组,对象,函数),都有一个__proto__(隐式原型)属性,属性值是一个普通的对象
console.log(obj.__proto__);
console.log(arr.__proto__);
console.log(fn.__proto__);
// 3.全部的函数,都有一个prototype属性(显式原型),属性值是一个普通的对象
console.log(fn.prototype);
// 4.全部的引用类型(数组,对象,函数),__proto__属性指向它的构造函数的prototype属性值
console.log(obj.__proto__===Object.prototype);
// 5.当视图获得一个对象的某个属性的时候,若是这个对象自己没有这个属性,那么
去它的__proto__(即它的构造函数prototype)中去找
function Foo(name,age){
this.name = name;
}
Foo.prototype.alertName = function(){
alert(this.name);
}
//建立实例
var f = new Foo('zhangsan');
f.printName = function(){
console.log(this.name);
}
f.printName();//能够获取到printName这个属性直接获取
f.alertName();//获取不到alertName属性这是去找他的构造函数的prototype中去找
复制代码
原型链
var f = new Foo('zhangsan');
f.toString();复制代码
分析:
1.首先去自身的属性中去找toString
方法,没有则去隐式原型__proto__
去找
2.__proto__
指向其构造函数的显式原型Foo.prototype
,如没找到再去Foo.prototype
的隐式原型去找
3.Foo.prototype
的__proto__
又指向Object.prototype
显式原型,而后再寻找其隐式原型__proto__
即查询到toString
方法,这就是原型链
总结
Object
是全部对象的爸爸,全部对象均可以经过 __proto__
找到它 Function
是全部函数的爸爸,全部函数均可以经过 __proto__
找到它 Function.prototype
和 Object.prototype
是两个特殊的对象,他们由引擎来建立 除了以上两个特殊对象,其余对象都是经过构造器 new
出来的 函数的 prototype
是一个对象,也就是原型 对象的 __proto__
指向原型, __proto__
将对象和原型链接起来组成了原型链
做用域和闭包是一个老生常谈的问题,常常会出如今各大面试中。下面就来简单介绍一下,
执行上下文
// 示例代码:
console.log(a);//undefined
var a=100;
fn('zhangsan');// zhangsan 20
function fn(name){
age=20;
console.log(name,age);
var age;
}
// 解释:
// 范围 | 一段<script>标签或者一个函数
// 全局 | 变量定义,函数声明
// 函数 | 变量定义,函数声明,this,arguments 复制代码
this的指向
this
要在执行时才能确认,定义时没法确认,使用bind
的时候必需要使用函数表达式
// 示例代码:
var a = {
name:'A',
fn:function(){
console.log(this.name);
}
}
a.fn();//this===a
a.fn.call({name:'B'});//this==={name:'B'}
var fn1 = a.fn;
fn1();//this===window
// 分析:
// 1.做为构造函数执行
// 2.做为对象属性执行
// 3.做为普通函数执行
// 4.call apply bind
function fn1(name){
alert(name);
console.log(this);
}
fn1.call({x:100},'zhangsan');//call第一个参数就是this
// 执行后弹出'zhangsan' 打印{x:100}
var fn2 = function (name){
alert(name);
console.log(this);
}.bind({y:200})
fn2('zhangsan');
// 执行后弹出'zhangsan' 打印{y:200}复制代码
做用域
// 1.没有块级做用域
if(true){
var name = 'zhangsan';
}
console.log(name);
// 2.有函数和全局做用域
var a = 100;
function fn(){
var a = 200;
console.log('fn',a);
}
console.log('global',a);
fn();
// 3.自由变量
var a = 100;
function fn(){
var b = 200;
console.log(a);//当前做用域没有定义的变量叫作自由变量
console.log(b);
}
// 注:这里打印a这个自由变量,则会去它的父级做用域去查询a,所谓的父级做用域就
是在函数定义的时候的做用域
// 4.做用域链
var a = 100;
function F1(){
var b = 200;
function F2(){
var c = 300;
console.log(a);//a是自由变量,先去父级F1中找a,没有再去F1的父级做用域找
console.log(b);//b是自由变量,先去父级F1中找b,找到后打印
console.log(c);
}
F2();
}
F1();
// 注:F2的父级做用域是F1,F1的父级做用域是全局,这就是做用域链复制代码
什么是闭包
闭包的定义很简单:函数 A 返回了一个函数 B,而且函数 B 中使用了函数 A 的变量,函数 B 就被称为闭包。
function A() {
let a = 1
function B() {
console.log(a)
}
return B
}复制代码
经典面试题,循环中使用闭包解决 var 定义函数的问题
for ( var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}复制代码
首先由于 setTimeout 是个异步函数,全部会先把循环所有执行完毕,这时候 i 就是 6 了,因此会输出一堆 6。
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i);
}复制代码
到这里咱们插入两个常见的问题写出如下程序的输出结结果
var a = 6;
setTimeout(function () {
console.log(a);
a = 666;
}, 1000);
setTimeout(function () {
console.log(a);
a = 777;
}, 0);
a = 66;
//分析
// 1.执行第一行a=6
// 2.执行setTimeout后,传入其中的函数会被暂存不会当即执行
// 3.执行最后一行a=66
// 4.等待全部程序执行完,处于空闲状态时,当即查看有没有暂存的执行队列
// 5.发现暂存的执行函数,如没有等待时间则当即执行,即第二个setTimeout,输出a=66,执行a=777
// 6.暂存的执行函数有等待时间的,1秒后输出a=777,再给a赋值666,所以会先输出66,1秒后输出777复制代码
请输出如下程序的结果,并简单分析一下过程
代码1: var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
console.log(object.getNameFunc()());
复制代码
|
代码2: var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
console.log(object.getNameFunc()());复制代码
|
// 分别输出 The Window, My Object
// 1.object.getNameFunc()返回一个函数,再调用()执行的时候上下文this为全局。
// 2. object.getNameFunc()返回一个函数的时候执行了that=this;,这时候that缓存的是上下文的this。即为 object,再调用()执行方法的时候打印的就是object的name
在滚动事件中须要作个复杂计算或者实现一个按钮的防二次点击操做。
这些需求均可以经过函数防抖动来实现。尤为是第一个需求,若是在频繁的事件回调中作复杂计算,颇有可能致使页面卡顿,不如将屡次计算合并为一次计算,只在一个精确点作操做。
PS:防抖和节流的做用都是防止函数屡次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的状况下只会调用一次, 而节流的 状况会每隔必定时间(参数wait)调用函数。
防抖动和节流本质是不同的。防抖动是将屡次执行变为最后一次执行,节流是将屡次执行变成每隔一段时间执行。
袖珍版的防抖理解一下防抖的实现:
// func是用户传入须要防抖的函数
// wait是等待时间
const debounce = (func, wait = 50) => {
// 缓存一个定时器id
let timer = 0
// 这里返回的函数是每次用户实际调用的防抖函数
// 若是已经设定过定时器了就清空上一次的定时器
// 开始一个新的定时器,延迟执行用户传入的方法
return function(...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, wait)
}
}
// 不难看出若是用户调用该函数的间隔小于wait的状况下,上一次的时间还未到就被清除了,
并不会执行函数
复制代码
这里只是简单说明一下这个概念,节流函数的实现,感兴趣的能够尝试本身写一下。
let a = {
age: 1
}
let b = a
a.age = 2
console.log(b.age) // 2
复制代码
从上述例子中咱们能够发现,若是给一个变量赋值一个对象,那么二者的值会是同一个引用,其中一方改变,另外一方也会相应改变。
一般在开发中咱们不但愿出现这样的问题,咱们可使用浅拷贝来解决这个问题。
浅拷贝
首先能够经过 Object.assign
来解决这个问题。
let a = {
age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
复制代码
固然咱们也能够经过展开运算符(…)来解决
let a = {
age: 1
}
let b = {...a}
a.age = 2
console.log(b.age) // 1
复制代码
一般浅拷贝就能解决大部分问题了,可是当咱们遇到以下状况就须要使用到深拷贝了
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = {...a}
a.jobs.first = 'native'
console.log(b.jobs.first) // native
复制代码
浅拷贝只解决了第一层的问题,若是接下去的值中还有对象的话,那么就又回到刚开始
深拷贝
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE复制代码
可是该方法也是有局限性的:
在遇到函数、 undefined
或者 symbol
的时候,该对象也不能正常的序列化
let a = {
age: undefined,
sex: Symbol('male'),
jobs: function() {},
name: 'wb'
}
let b = JSON.parse(JSON.stringify(a))
console.log(b) // {name: "wb"}
复制代码
你会发如今上述状况中,该方法会忽略掉函数和 undefined
。
可是在一般状况下,复杂数据都是能够序列化的,因此这个函数能够解决大部分问题,而且该函数是内置函数中处理深拷贝性能最快的。固然若是你的数据中含有以上三种状况下,可使用递归来实现一个深拷贝函数(此部分后续会在vue篇进行实现),为实现深拷贝函数,这里再引入一个知识点
咱们知道可使用typeof函数来判断一个类型,可是使用typeof判断确不是很准确,以下
那么若是我要精确判断一个类型是对象仍是数组,或者是函数该如何实现呢?
这里咱们可使用Object.prototype.toString.call(obj) 来精确判断类型
function typeOf (obj) {
const toString = Object.prototype.toString
const map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object'
}
return map[toString.call(obj)]
}
复制代码