js基础
作用域
前言
都知道JS存在三种作用域,即全局,函数,块级。其实是没有块级作用域的,但我们可以通过闭包来实现它。
函数作用域
很好理解,在函数内部声明的变量在函数调用后会销毁
块级作用域
在if,while,for等代码体用{}包括的,成为块级作用域,但是在JS中没这说法。
比如
1 | for(var i=0;i<10;i++){ |
问题出来了,按理说正常情况是控制台输出0~9,弹出undefined,但是alert出来的却是9,
那这足以说明咱们的JS是不存在块级作用域的,块里面的变量是被挂载在window上的。
那么这会引发一个问题,看下面代码:
1 | var arrs=[]; |
这里你不管执行 第几个函数都是打印9,原因有两个,第一因为JS中不存在块级作用域,i的声明是挂载在window上的,再一个,函数在执行的时候获取到的i为循环最后一次+1的i,所以恒定为9。
解决办法:利用闭包虚构一个块级作用域,并将i的值传入。
闭包
注意事项:
- 函数的传参是传入的复制值
- 闭包其实就是能获取到函数内部变量的函数
1
2
3
4
5
6
7
8
9
10var a1 = function(){
var n = 999;
a2 = function(){
n++;
}
return n;
}
console.log(a1()); // 999
a2();
console.log(a1()); // 1000
闭包的两个主要作用:
- 获取函数内部的变量
- 使函数内部的变量不被回收
1
2
3
4
5
6
7
8
9
10
11var arrs=[];
for(var i=0;i<10;i++){
(function(i){
arrs[i]=function(){
console.log(i);
}
})(i) //将循环的i的值传入
}
arrs[0] // 0
arrs[1] // 1
//..........
此时当函数执行的时候会拿到这个立即执行函数的i的值,而立即执行函数的值是for循环传入的,所以就能正常输出0~9。
JavaScript数据类型
分为基本类型和引用类型
值类型(基本类型):
字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol
引用数据类型:
对象(Object)、数组(Array)、函数(Function)
判断数据类型的方法
1 | var arr = []; |
typeof
1 | console.log(typeof arr) // object 对于复杂类型的对象只能返回object |
instanceof
1 | console.log(arr instanceof Array) // true 可以判断变量的引用类型 |
Object.prototype.toString.call()
1 | Object.prototype.toString.call(arr) == "[object Array]"; //基本类型和引用类型都可以得到 |
undefined和null的区别
null表示”没有对象”,即该处不应该有值。典型用法是:
- 作为函数的参数,表示该函数的参数不是对象。
- 作为对象原型链的终点。
- 释放内存
1 | Object.getPrototypeOf(Object.prototype) // null |
undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义。典型用法是:
- 变量被声明了,但没有赋值时,就等于undefined。
- 调用函数时,应该提供的参数没有提供,该参数等于undefined。
- 对象没有赋值的属性,该属性的值为undefined。
- 函数没有返回值时,默认返回undefined。
1 | console.log(typeof undefined); //undefined |
Boolean与条件判断
首先明白两点
- 只有false,””,0,null,NaN,undefined会被转换成false。
- 任何类型的数据在与布尔值比较时都会被转换为number类型。
1 | console.log(Boolean(false)); //false |
其实我们的if语句的条件没有运算符的情况下就是执行了Boolean操作
1 | if(x){ |
那么能看懂下面的东西就对这块掌握的比较好了
1 | console.log(Number([])); //0 |
Symbol
Symbol是由ES6规范引入的一项新特性,它的功能类似于一种标识唯一性的ID
创建方式:
Symbol()
1 | const name = Symbol('username') |
特性一:每一次创建的Symbol都是唯一的,就算描述一致
1 | console.log(Symbol("username") === Symbol("username")); // false |
特性二:可以作为对象的属性,只能以[]方式访问
1 | const name = Symbol('username'); |
特性三:可以被外部访问,但不能被枚举
1 | const name = Symbol('username'); |
全局Symbol,使用Symbol.for可以保证创建的symbol唯一
1 | console.log(Symbol.for("username") === Symbol.for("username")); // true |
Bind函数
下面是MDN的bind函数实现,这里对下面的一些写法进行剖析。
bind做了什么?
- 调用bind,就会返回一个新的函数。
- 新的函数里面的this就指向bind的第一个参数,同时this后面的参数会提前传给这个新的函数。调用该新的函数时,再传递的参数会放到预置的参数后一起传递进新函数。
实现:
1 | if (!Function.prototype.bind) { |
Array.prototype.slice.call(arguments, 1)
我们都知道arguments是一个类数组对象,所以这里调用数组的slice方法给arguments使用。因为arguments能像访问数组的形式进行访问(arguments[0]),且具有length属性,因此我们可以得到返回的一个有length长度的数组。
后面传入参数1,是slice(start, end)中的一个参数start,表示从arguments的小标为1,即第二个参数开始切割。 这里是将bind函数的参数数组取出来,第一个参数不要(就是不要oThis)也就是要被绑定方法的那个对象fNOP和fBound函数
1
2
3
4
5//假设bind过程如下
function a(){
this.name = 'a';
}
var b = a.bind(oThis);正常调用bind函数
返回一个新函数,该函数的this指向调用bind方法的一个参数。即代码里的:1
2
3
4//bind函数内部解析
fToBind = this; //这里的this就是a;
fToBind.apply(oThis,........................) //这里便是将oThis替换a函数的this;
return fBound; //即b的this便是指向oThis;到这里正常调用bind并传参就完成了
bind返回的函数作为构造函数的情况。首先我们得理解new做了什么,如下:
1
2
3
4
5var o = new fun();
//实际操作
var o = new Object();
o.__proto__ = Foo.prototype;
fun.call(o);这里可以看到,当fun被当做构造函数执行时,fun的this指向的是o,且o可以访问fun的原型的属性。
1 | fNOP = function () {} ; |
这样便兼容了新函数作为构造函数的情况。
这里有一个疑问,为什么新函数的要继承a函数的原型,以下是mdn的解释:
bind() 函数会创建一个新绑定函数(bound function,BF)。绑定函数是一个exotic function object(怪异函数对象,ECMAScript 2015中的术语),它包装了原函数对象。调用绑定函数通常会导致执行包装函数。
基本算法
冒泡算法
作为经典入门算法之一,因为其每轮的比较都会确认一个最大(最小)的数,就像冒出来一样而得名。
设计思路:
- 确定程序执行次数
- 两两比较,按照规定交换位置
程序实现
1 | const arr = [10,5,6,25,1000,1]; |
按照上面的数组来说就是
第一轮…….
第一次:10和5比较,交互位置,此时数组为[5,10,6,25,1000,1]
第二次:10和6比较,交换位置,此时数组为[5,6,10,25,1000,1]
…………………………………………..
第length-1次:此时数组为[5,6,10,25,1,1000]
可以看到我们已经确定了一个最大数1000,那么接下来就将[5,6,10,25,1]继续执行第二轮
第二轮…….
第length-1轮…….得到最终排好序的数组
冒泡算法效率低,我们在此基础上尽量的去优化其效率
重点解释两个地方
外层循环次数length-1
- 按照我们的算法设计逻辑,因为每一次都会有一个数被确定,所以当执行了l-1轮,就剩最后一个数。自然是最小(最大)的,所以无需再执行
内层循环次数length-1-i
- 首先一个数组两两比较需要的次数为length-1次,上面我们说到每轮都会确定一个数,那么确定的数无需进行比较所以再-i
选择排序
类似与冒泡,不同的是每轮确定一个最小(最大)的数,再将其依次安放
程序实现
1 | const arr = [10,5,6,25,1000,1]; |
快速排序
确定一个中间值,和其他元素进行比较,小的放左边数组,大的放右边数组。再递归左边的数组和右边的数组,最后合并
1 | const arr = [10,5,6,25,1000,1]; |