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