前端基础主要分为三大类:ECMAScript、DOM、BOM
ECMAScript:语法、变量、关键字、保留字、值、原始类型、引用类型、运算、对象、继承、函数
DOM:docment、object、model,操作文档,遵守W3C规范
BOM:browser、object、model,操作浏览器,包含滚动条、窗口的宽高等
JS
命名规范:
- 不能以数字开头
- 关键字和保留字不能用
- 语义化
JS分为两种类型:原始类型和引用类型
- 原始类型有:Number String Boolean undefined null symbol
- 引用类型有:Object Array Function Data RegExp
原始值存在栈内存中,引用类型地址存在栈内存中,值存在堆内存中。
首先原始类型存储的都是值,是没有函数可以调用的,比如 undefined.toString()
这里'1'.toString() 却可以使用,为什么呢?其实在这种情况下,'1' 已经不是原始类型了,而是被强制转换成了 String 类型也就是对象类型,所以可以调用 toString 函数。 除了会在必要的情况下强转类型以外,原始类型还有一些坑。
其中 JS 的 number 类型是浮点类型的,在使用中会遇到某些 Bug,比如 0.1 + 0.2 !== 0.3。因为 JS 采用 IEEE 754 双精度版本(64位),并且只要采用 IEEE 754 的语言都有该问题。
我们都知道计算机是通过二进制来存储东西的,那么 0.1 在二进制中会表示为
// (0011) 表示循环
0.1 = 2^-4 * 1.10011(0011)
我们可以发现,0.1 在二进制中是无限循环的一些数字,其实不只是 0.1,其实很多十进制小数用二进制表示都是无限循环的。这样其实没什么问题,但是 JS 采用的浮点数标准却会裁剪掉我们的数字。
IEEE 754 双精度版本(64位)将 64 位分为了三段
- 第一位用来表示符号
- 接下去的 11 位用来表示指数
- 其他的位数用来表示有效位,也就是用二进制表示 0.1 中的 10011(0011)
那么这些循环的数字被裁剪了,就会出现精度丢失的问题,也就造成了 0.1 不再是 0.1 了,而是变成了 0.100000000000000002
0.100000000000000002 === 0.1 // true
那么同样的,0.2 在二进制也是无限循环的,被裁剪后也失去了精度变成了 0.200000000000000002
0.200000000000000002 === 0.2 // true
所以这两者相加不等于 0.3 而是 0.300000000000000004
0.1 + 0.2 === 0.30000000000000004 // true
那么可能你又会有一个疑问,既然 0.1 不是 0.1,那为什么 console.log(0.1) 却是正确的呢?
因为在输入内容的时候,二进制被转换为了十进制,十进制又被转换为了字符串,在这个转换的过程中发生了取近似值的过程,所以打印出来的其实是一个近似值,你也可以通过以下代码来验证
console.log(0.100000000000000002) // 0.1
那么说完了为什么,最后来说说怎么解决这个问题吧。其实解决的办法有很多,这里我们选用原生提供的方式来最简单的解决问题
parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true
String 类型是不可变的,无论你在 String 类型上调用何种方法,都不会对值有改变。
另外对于 null 来说,很多人会认为他是个对象类型,其实这是错误的。虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。
typeof 对于原始类型来说,除了 null 都可以显示正确的类型 number,string,boolean,object,undefined,function
typeof 对于对象来说,除了函数都会显示 object,所以说 typeof 并不能准确判断变量到底是什么类型
typeof(null)//object
typeof(NaN)//object
typeof(undefined)//undefined
typeof(a)//undefined,a未定义;如果不是typeof()则会报错;
typeof(typeof(a))//string typeof(类型名)都为string
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
封装typeof,能够正确显示object类型
function myTypeof(val){
var type = typeof(val),
toStr = Object.prototype.toString,
res = {
'[object Array]' : 'array',
'[object Object]' : 'object',
'[object Number]' : 'object number',
'[object String]' : 'object string',
'[object Boolean]' : 'object boolean'
};
if(val === null){
return 'null'
}else if(type === 'object'){
var ret = toStr.call(val);
return res[ret];
}else{
return type;
}
}
运算符
“+”符号:
- 数学运算
- 字符串拼接
var c;
c = "str" + true; //strtrue
c = "str" + undefined;//strundefined
c = "str" + null;//strunull
NaN非数,not a Number; NaN与包括自己在内的任何东西都不相等
Infinity 无限大
-Infinity 负无穷
% 取模、取余
比较运算符:>, <, >=, <=, ==, ===, !=, !==
优先级:声明 > 运算 > 赋值
四句话总结 == 和 ===:相等不看数据类型,全等要看数据类型,==先强制转换再比较,===仅比较不转换。
转换规则:
- 1、如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值——false转换为0,而true转换为1;
- 2、如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值
- 3、如果一个操作数是对象,另一个操作数不是,则调用对象的toString()方法,用得到的基本类型值按照前面的规则进行比较
- 4、null 和undefined 是相等的
- 5、要比较相等性之前,不能将null 和 undefined 转换成其他任何值
- 6、NaN与包括自己在内的任何东西都不相等
- 7、如果两个操作数都是对象,则比较它们是不是同一个对象,如果两个操作数都指向同一个对象,则相等操作符返回 true;否则, 返回false
&&遇到真就往后走,遇到假或走到最后就返回当前值
||遇到假往后走,遇到真或走到最后就返回当前值
undefined, null, NaN, "", 0, false,除了这些以外全部为真
[] == ![]
//true !的优先级更高,先将[]转换为Boolean值,!除了上述为真意外,其他都为假,根据第一条,再将false转换为0,![] -> false -> 0
//[].toString = '' -> 根据第二条,Number('') = 0,所有相等
{} == !{}//false !{} -> false -> 0, ({}).toString = "[object, Object]",Number("[object, Object]") -> NaN
{} == {}//false 相同时比较的是引用地址,因为存储的地方不同所以不相等
[] == []//false 相同时比较的是引用地址,因为存储的地方不同所以不相等
显式类型转换
Number(null)//0
Number(undefined)//NaN
Number(NaN)//NaN
Number(true)//1
Number(false)//1
Number('a')//NaN
Number('123')//123
Number('abc123')//NaN
parseInt('123abc');//123
parseInt('abc123');//NaN
parseInt('3.14');//3
parseInt('10', 16)//16 第二个参数表示16进制
parseInt('b', 16)//11 第二个参数表示16进制
parseInt(true);//NaN,false,true,null,undefined 都为NaN
parseFloat(3.1415).toFixed(2)//3.14
隐式类型转换
var a = '123';
a ++;
console.log(a);//124,先将字符串通过Number()转换为数字123,再进行++
运算符 * / - % 会将string类型转换为number类型进行运算
而+号在前会将string类型转换为number类型,+号在后会将number转换为string类型
百度考题:
undefined == 0//false
null == 0//false
undefined == null//true
undefined === null//false
var num = '123';
console.log(typeof(+num))//number
isNaN判断是否非数
console.log(isNaN('123'))//false,先经过Number()隐式转换
console.log(isNaN('a'))//false
console.log(isNaN('null'))//false
console.log(isNaN('undefined'))//false
斐波那契数列
实现:1 1 2 3 5 8
//递归 规律和出口
//规律: n>2时,n3 = n1 + n2;
//出口: n <= 2
function fb(n){//1 1 2 3 5 8
if(n <= 2) return 1;
return fb(n-1) + fb(n-2);
}
预编译
1、检查通篇的语法错误
1.5、预编译的过程
2、解释一行,执行一行
函数声明整体提升,变量只有声明提升,赋值不提升
AO activation object 活跃对象,函数上下文
1、寻找形参和变量声明
2、实参值赋值给形参
3、找函数声明赋值
4、执行
GO global object 全局上下文
1、找变量
2、找函数声明
3、执行
闭包
当内部函数被返回到外部,一定会产生闭包。过度的闭包可能会导致内存泄露,或加载过慢,闭包产生原来的作用域链不释放。
function test1(){
function test2(){
var b = 2;
console.log(a);
}
var a = 1;
return test2;
}
var c = 3;
var test3 = test1();
test3();
立即执行函数
IIFE immediately-invoked function expression
自动执行,执行完成以后立即释放
(function test(){})();//test名称可有可无,因为执行完成立即销毁
(function(){}());//W3C建议
任何被括号括起来的都是表达式,一定是表达式才能被执行符号执行
阿里笔试题:
var a = 10;
if(function b(){}){
a += typeof(b);
}//(function b(){})是一个函数表达式,因此函数名被忽略,typeof(b)则返回undefined,最后结果为10undefined
函数声明变成表达式的方法,在函数前面加上 + - ! || &&
面试题:
function test(a){
console.log(++a);
}(6)//6 不报错,但也不会运行,会被认为是表达式
function test(a){
console.log(a);
}()//报错,不是表达式
function test(){
var arr = [];
for(var i = 0; i < 10; i++){
arr[i] = function(){
console.log(i + ' ');
}//匿名函数取的是test中OA的变量值,且匿名函数没有被执行,所以最后i已变成10
}
return arr;
}
var myArr = test();
console.log(myArr);// (10) [ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ]
for(var j = 0; j < 10; j++){
myArr[j]();//10 10 10 10 10 10 10 10 10 10
}
如果将匿名函数变为立即执行函数,则输出0-9
for(var i = 0; i < 10; i++){
(function(j){
arr[j] = function(){
console.log(j + ' ');
}
})(i)//匿名函数取的是test中OA的变量值,且匿名函数没有被执行,所以最后i已变成10
}
逗号运算符
括号的逗号运算,返回的永远是最后一个
var num = (2, 1, 5);
console.log(num);//只返回最后一个
var fn = (function test1(){
return 1;
},
funtion test2(){
return '2'
})();
console.log(typeof(fn));//string 返回的是test2(),然后立即执行
包装类
undefined/null.toString()会报错
系统内置的构造函数都有.toString()方法,而且不一样。 Number,Boolean,Array对原型上.toString()方法进行了重写
var str = 'abc';
console.log(str.length);//3
//相当于 console.log(new String(str).length);
var str2 = 'abc';
str2.length = 1;//这里经过new String(str2).length = 1;由于没有地方存储,只是一个临时容器,因此便删除,长度不变
console.log(str2.length);//3
var name = 'languiji';
name += 10;
var type = typeof(name);
if(type.length === 6){
type.text = 'string';
}
console.log(type.text);//undefined
//这里有陷阱,type = 'string'是原始值,是没有属性的
面试题:
var num = 1;
var obj = {};
var obj2 = Object.create(null);
document.write(num);// 1
document.write(obj);// [object Object] 表示对象类型的Object构造函数
document.write(obj2);//报错,因为没有原型,所以没有.toString()方法
//document.write会隐式调用.toString()方法
阿里面试题:
第二个函数不会报错,但也不会执行
原型、原型链
原型prototype其实是function对象的一个属性,也是一个对象
原型链的顶端 -> Object.prototype,通过__proto__向上查找原型
prototype是定义构造函数构造出的每个对象的公共祖先,所有被该构造函数构造出的对象都可以继承原型上的属性和方法。
需要配置的项写在构造函数中,方法一般写在prototype上
constructor -> 构造函数本身
可以通过prototype更改构造函数 -> 属于实例化对象
__proto__:容器通过__proto__指向prototype
Car.prototype.name = 'Benz';
function Car(){}
var car = new Car();
Car.prototype = {
name: 'Mazda'
}
console.log(car.name)//Benz
Car.prototype.name = 'Benz';
function Car(){}
var car = new Car();
Car.prototype.name = 'Mazda'
console.log(car.name)//Mazda
//Car.prototype.name 是属性重新赋值,而 Car.prototype = {name: 'Mazda'}是整个prototype重写,是不一样的
//书写插件的方式:
;(function(){
function Test(){
Test.prototype = {}
window.Test = Test
}
})();
var test = new Test();
var obj = {}//字面量
var obj = new Object();//不推荐
//没有区别
Object.create(对象/null)//提供了一个自定义原型的功能
//创建对象的prototype指向参数,参数一般为xxx.prototype;如果为null,则没有prototype
var test = { num : 2 }
var obj3 = Object.create(test)//test作为obj3的原型
function Obj(){}
Obj.prototype.num = 1;
var obj = new Obj();
new:1、实例化obj
2、调用构造函数Obj的初始化属性和方法
3、指定实例对象的原型
不是所有的对象都继承于Object.prototype
__proto__能更改,但不能自造
var obj = Object.create(null);
var obj1 = {
count: 2
}
Obj.__proto__ = obj1;
console.log(obj.count)//undefined
call/apply
都是更改this的指向,其中apply用得多一些
- 普通函数的this指向window
- 构造函数的this指向实例化对象
- 全局的this指向window
- 预编译函数this指向window
唯一的区别就是参数不一样,apply传的是数组
判断一个对象是不是数组:
var a = [];
var str = Object.prototype.toString.call(a);
if(str === '[object Array]'){
console.log('是数组');
}else{
console.log('不是数组');
}
function b(x, y, a){
arguments[2] = 10;
console.log(a);
}
b(1, 2, 3)// 10
callee/caller 严格模式下会报错
function test(a, b, c){
console.log(arguments.callee.length);//3 callee返回实参列表所对应的函数
console.log(test.length);//3
console.log(arguments.length);//2
}
test(1, 2)
//累加器 递归
var sum = (function(n){
if(n <= 1){
return 1;
}
return n + arguments.callee(n - 1);//callee实参列表上的属性
})(100)
console.log(sum);//5050
function test1(){
test2();
}
test1();
function test2(){
console.log(test2.caller);//ƒ test1(){test2()} caller返回当前被调用函数的函数引用
}
深拷贝、浅拷贝
浅拷贝
function clone(target, origin){
var tar = target || {};
for(var key in origin){
if(origin.hasOwnProperty(key)){
tar[key] = origin[key];
}
}
return tar;
}
深拷贝
function deepClone(target, origin){
var tar = target || {},
toStr = Object.prototype.toString,
arrType = '[object Array]';
for(var key in origin){
if(origin.hasOwnProperty(key)){
if(typeof(origin[key]) === 'object' && origin[key] !== null){
toStr.call(origin[key]) === arrType ? target[key] = [] : target[key] = {};
deepClone(target[key], origin[key])
}else{
tar[key] = origin[key];
}
}
}
return tar;
}
数组
修改原数组的方法有:push,unshift,pop,shift,reverse,splice,sort
push尾部添加,unshift头部添加,返回的是执行了方法以后数组长度
//重写push方法
Array.prototype.myPush(){
for(var i = 0; i < arguments.length; i++){
this[this.length] = arguments[i];
}
return this.length;
}
pop尾部删除,shift头部删除,返回的是删除的值
reverse倒序,返回倒序之后的数组
var arr3 = [1, 3, 4, 2, 5];
arr3.reverse();//[5, 2, 4, 3, 1]
sort()升序排列,返回排序以后的数组,按照ASCII码来排序
var arr = [-1, -5, 8, 0, 2];
arr.sort();//[-1, -5, 0, 2, 8]
sort排序:1、参数a, b
2、返回值: 负值,a就排前面
正值,b就排前面
0保值不动
var arr = [-1, -5, 8, 0, 2];
arr.sort(function(a, b){
if(a > b){
return 1;
}else{
return -1
}
})
arr;// [-1, -5, 0, 2, 8]
可改成:
arr.sort(function(a, b){
return a-b;//升序
return b-a;//降序
})
随机排序:
arr.sort(function(a, b){
return Math.random() - 0.5;
})
splice(开始项的下标,剪切长度,剪切以后从最后一位开始添加的数据),返回下标值
concat 合并,只有数组才能合并
var arr1 = [1, 2, 3],
arr2 = [3, 4],
arr3;
arr3 = arr1.concat(arr2);
console.log(arr3);// [1, 2, 3, 3, 4]
toString()转字符串
[1,2,3].toString();//'1,2,3'
slice(开始截取的元素下标, 结束截取元素的前一位)
var arr = [1, 2, 3, 4, 5, 6];
arr.slice(3, 5);//[4, 5]
join
var arr = ['a', 'b', 'c', 'd'];
var str1 = arr.join();
console.log(str1);//'a,b,c,d'
var str2 = arr.join(0);
console.log(str2);//'a0b0c0d'
split 将字符串变成数组
var str = 'a,b,c,d';
str.split(',', 2);//['a', 'b']
indexOf()可返回某个特定的字符串值在字符串中首次出现的位置,如果没有出现返回-1;find返回符合条件的元素;findIndex返回索引值
寻找当前元素的下标:Arr.prototype.indexOf.call(父级集合, 当前元素);或者[].indexOf.call(父级集合, 当前元素)
类数组 arguments
1.一定要有下标值;2.一定要有length属性
将类数组转化为数组的方法:Array.prototype.slice.call(arguments);
重写unshift
Array.prototype.myUnshift = function(){
var arr = Array.prototype.slice.call(arguments);
var newArr = arr.concat(this);
return newArr.length;
}
数组去重
Array.prototype.unique = function(){
var temp = {}, newArr = [];
for(var i = 0; i < this.length; i++){
if(!temp.hasOwnProperty(this[i])){
temp[this[i]] = this[i];
newArr.push(this[i])
}
}
return newArr;
}
字符串去重
String.prototype.unique = function(){
var temp = {},
newStr = '';
for(var i = 0; i < this.length; i++){
if(!temp.hasOwnProperty(this[i])){
temp[this[i]] = this[i];
newStr += this[i];
}
}
return newStr;
}
找出第一个不重复的项
function test(str){
var temp = {};
for(var i = 0; i < str.length; i++){
if(temp.hasOwnProperty(str[i])){
temp[str[i]]++;
}else{
temp[str[i]] = 1;
}
}
for(var key in temp){
if(temp[key] === 1){
return key;
}
}
}
JS错误类型
1、SyntaxError 语法错误
- 变量名不规范
- 基本语法错误
2、ReferenceError 引用错误
- 变量或函数未被声明
- 给无法被赋值的对象赋值的时候
3、RangError 范围错误
- 数组长度赋值为负数
- 对象方法参数超出可执行范围
4、TypeError 类型错误
- 调用不存在的方法
- 实例化原始值
5、URI Error URI错误
6、Eval Error eval函数执行错误
eval:把json字符串转换为可循环json对象
手动抛出错误
try{
//正常执行
}catch(e){
//e.name + e.message
}finally{
//不管有错没错都执行,与外部函数或变量是一样的,因此可有可无
}
throw()//自定义错误信息
严格模式下:
- 函数参数不能重复
- with函数
- caller/callee
- this指向
'use strict'
function test(){
console.log(this)//undefined
}
- var a = b = 1;//未声明报错
- eval(改变作用域):非严格模式下 -> window,严格模式下 -> 指向自己的作用域
垃圾回收机制
JS引擎有自动的回收的功能
- 找出不再使用的变量
- 释放其占用内存
- 固定的时间间隔运行
闭包销毁
function test(){
var a = 1;
return function(){
a++;
console.log(a)
}
}
var test = test1();
test = null;//被销毁
JSON 必须用双引号
JSON本质是对象,JSON集合本质是数组
所有编程语言都离不开的三大数据类型:
- scalar 标量 -> 字符串和数字
- sequence 序列 -> 数组和列表 list array
- mapping 映射 -> 键值对 key:value
XML 是服务器端与服务器端之间通信的语言
JSON.parse(data);//把JSON字符串转化为JSON对象 JSON.stringify(obj);//把JSON对象转化为JSON字符串
数组扩展
forEach 遍历
[].forEach(function(elem, index, array){}, thisArg); //第二个参数的作用是:更改第一个参数函数内部this指向,写什么指向什么;且会强行转化为对象,但null,undefined则指向window
重写forEach
Array.prototype.myForEach = function(fn){
var arr = this,
len = arr.length,
arg2 = arguments[1] || window;
for(var i = 0; i < len; i++){
fn.apply(arg2, [arr[i], i, arr]);
}
}
filter 筛选、过滤,返回数组
var newArr = data.filter(function(elem, index, array){
if(elem.is_free === '1'){
return true
}// -> return elem.is_free === '1'
})
重写:filter
Array.prototype.myFilter = function(fn){
var arr = this,
len = arr.length,
arg2 = arguments[1] || window,
newArr = [];
for(var i = 0; i < len; i++){
fn.apply(arg2, [arr[i], i, arr]) ? newArr.push(arr[i]) : '';
}
return newArr;
}
map映射
map()返回一个新数组,原数组不会改变
Array.prototype.myMap = function(){
var arr = this,
len = arr.length,
arg2 = arguments[1] || window,
newArr = [],
item;
for(var i = 0; i < len; i++){
item = deepClone(arr[i]);
newArr.push(fn.apply(arg2, [item, i, arr]))
}
}
every 用于检测数组所有元素是否否符合指定条件,如果有一个不满足,则false,如果所有元素都满足条件,则返回true,不改变原数组
Array.prototype.myEvery = function(fn){
var arr = this,
len = arr.length,
arg2 = arguments[1] || window,
res = true;
for(var i = 0; i< len; i++){
if(!fn.apply(arg2, [arr[i], i, arr])){
res = false;
break;
}
}
return res;
}
some
- 存在一个满足条件就停止遍历,return后面表达式
- 返回一个boolean值
var res = data.some(function(elem, index, array){
console.log(elem);
return elem.is_free == '0';
}, {name: 'test'})
reduce
归纳函数,不改变this指向
[].reduce(function(prev, elem, index, arr){},initialValue);//initialValue 初始值,必填
第一次prev和initialValue是同一个值,其后prev是其返回值
var initialValue = [];
var nArr = data.reduce(function(prev, elem, index, arr){
if(elem.is_free === '1'){
prev.push(elem);
}
return prev;
}, initialValue)
reduceRight 倒过来
重写reduce
Array.prtotype.myReduce = function(fn, initialValue){
var arr = this,
len = arr.length,
arg2 = arguments[2] || window;
for(var i = 0; i< len; i++){
initialValue = fn.apply(arg2, [initialValue, arr[i], i, arr]);
}
return initialValue;
}
通用模板:
<script src="text/html">
<p>{{opt}}</p>
</script>
function setTplToHTML(tpl, regExp, opt){
return tpl.replace(regExp(), function(node, key){
return opt[key];
})
}
function regTpl(){
return new RegExp(/{{(.*?)}}/, 'gim')
}