JS学习笔记(更新中)

基础部分

随便bb

针对最近才开始系统学习js,再次进行一些记录。(TMD我不更了-----by 2020.11.11)

js和es

之前挺疑惑的,为什么讨论js别人总是说ES。javascript是一种语言,而ES(ECMAscript)是一种制定的标准,而js就是符合这个标准的一门语言。目前最新的标准是ES6.

js的strict模式

在js代码的第一行加上'use strict'

作用: 在strict模式下, 未使用var申明变量就使用的,将导致运行错误. 对于this指针在部分情况下也会指向undefine而不是window, 这样如果this指向不当将会报错. (之后提及)

js的数据类型

记一次忘一次,写成笔记天天看,看你还忘。ES5六种:Number, Boolean, undefine, null, object, string.
es6新增Symbol:新建对象用不相等,技术传入参数相同。
谷歌67有种bigInt,但是好像用的人不多。
js居然可以对浮点数求余,震惊。。。。不知道其他语言支不支持,这个我还真是第一次见。比如:10.5 % 3; // 1.5
js的NaN(非数字)用isNaN()进行判断。
本来打算看看null和undefine的区别,看到一个老哥这么说:
“事实证明,这并没有什么卵用,区分两者的意义不大” 哈哈哈。多数情况下使用null。
nullundefined0NaN和空字符串''视为false

数值转换

其他类型转数字:

使用Number()

  • 字符串: 忽略前导零, 可识别负号/小数/十六进制数(返回对应的十进制数), 为空则返回0, 无法识别则返回NaN.
  • 对象: 调用valueOf(), 返回NaN则继续调用toString()

parseInt():(只识别字符串)

  • 读取第一个非空格字符, 如果不是负号或者数字, 返回NaN.如果是则一直读到非数字字符(小数点也会终止)
  • 0开头为八进制(ES5之后不支持), 0x开头为16进制
  • 如果字符串为空, 返回NaN(不是返回0)
  • 有第二个参数, 可指定进制(如果遇到超出进制的数则停止解读)

parsefloat():(只识别字符串, 和parseInt类似)

  • 遇到第一个小数点不会停止解读
  • 只能转化十进制数
  • 可解读e, 科学计数法

其他类型转换为字符串:

  • 调用toString()方法
  • String(): 可以对null和undefined操作
全局变量与局部变量
  • js是可以直接声明变量: a=1, 但是这样的变量是定义为全局变量.

  • var定义为局部变量.

  • let和const只在相应的块级.

  • 局部变量只在自己的执行域生效, 如果获取不到就在全局获取, 再获取不到就报错.

js的字符串

特殊字符用\转义。ASCII使用\x转义(后接)对应的ASCII值。\u表示unicode字符。\n换行、\t表tab、``可表示多行字符,不用一个个加\n(这是es6的标准)。
模板字符串:在字符串中插入相应的变量,便于显示某些动态的值:

var helloText=`my name is ${name}`

这里的字符串是用 **``**来扩起来,不是单引号

字符串通过属性length获取长度。
字符串变量可以通过下标获取具体位置的字符,但不能修改该字符,字符串是不可变的。

字符串是基本类型, 理论上应该是没有相应的属性和方法的, 但是在实际中调用时, JS会临时创建一个对象, 然后执行相应的方法, 完成后销毁该对象.

字符串对象的常用方法有:

  • charAt(index)/charCodeAt(index): 返回对应位置的字符/字符编码
  • indexOf(str, beg=0)/lastIndexOf(str, beg = 0): 获取指定字符串第一次/最后一次出现的位置, 第二个参数表示字符串开始的位置()检索区间为[beg, length-1]
  • search(str): 和indexOf类似, 不过不能设置初始位置, 但是str可以是正则表达式.
  • 提取部分字符串:
    • slice(start, end): 参数为负数时加上字符串长度的整倍数(不适用于IE8及以前版本). start>end时返回空串. 只有一个参数时默认最后一个参数为最后一个字符的位置.
    • substring(start, end) : 参数为负时视为0, 第二个参数缺省时同上
    • **substr(start, length) ** :第一个参数为负数时视为表示从尾部开始提取(此时会忽略第二个参数, 第一个参数的绝对值为提取字符串的长度). 第二个参数为负时视为0.
  • replace(oldstr, newstr): 用第二个参数的值替换第一个参数的值, 第一个参数可以是正则表达式.
  • toUpperCase()/toLowerCase(): 全部转换为大写/小写
  • concat(): 相加(多用+代替)
  • trim(): 去掉两端的空格
  • split(s): 分割字符串, 返回数组

字符串的所有方法都不会在原字符串上修改, 都是生成新的字符串返回

数组
  • js的数组可以指定长度,直接修改length可以改变数组长度,新增的值的后面都是undefine。
  • 可以通过下标赋值,对原数组没有的值会在后面追加。
常用方法:
  • indexOf(): 和字符串的一样, 可以获取第一个对应值的下标。没有则返回-1.
  • toString(): 转换成字符串, 每个元素用逗号分隔开
  • join(s),返回一个字符串,字符串使用传入的字符串参数连接。
  • slice(),原来的数组不会有变化,返回一个新的数组,传入第一个参数为开始的下标,第二个参数为结束的下标,返回的数组为这两个下标之间的值。含下不含上。如果没有传入第二个参数,就是默认为最后一个。
  • pop()和push()。最常用的两个方法, 尾部添加, 原数组修改。push返回数组长度, pop返回弹出元素. pop删除操作当数组不为空时返回删除的值,为空时不报错,返回undefine;新增操作返回新数组的长度。
  • unshift()和shift(),和上面两个类似,不过上面两个是从尾部操作,这俩是从头部操作。shift操作空数组同上pop.原数组修改.
  • **reverse()**数组反转。返回翻转后的数组, 原数组修改.
  • **splice(start, length, **args)**第一个参数为开始的索引;第二个参数为数量,表示从索引开始删除元素的数量,这个和slice()不一样;之后的参数都插入到数组中去。返回删除的元素。没有删除则返回[].
  • concat(),连接两个及以上arr,返回连接后的arr,原数组不会改变。
  • sort(fn): 传入一个回调函数,
  • forEach(fn): 回调函数传入三个参数, 数组元素, 元素索引值, 数组本身. 因为第三个参数就是数组本身, 可以直接通过索引值和数组引用修改数组. IE8及以下不支持.
  • map(fn), fn传入数组元素, 返回值作为新数组的元素. 原数组不改变, 生成一个新数组. 回调函数和forEach一样有三个参数, 也可以通过原数组引用和对应的索引值修改原数组.
  • filter(fn), 回调函数传入的第一个参数为数组元素, 返回的布尔值表示是否保留该元素. 另外两个参数同上. 原数组不改变.
  • reduce(fn), 回调函数传入四个参数, 后两个同上. 第一个参数是上一个回调函数的返回值(第一个回调函数的第一个参数是数组的第一个元素), 第二个参数为第二到最后一个元素中的某一个. 回调函数一共执行length-1次
  • 判断对象是否数组的方式:
    • Array.isArray(arr), 如果时数组则返回true, 支持所有支持ES5的浏览器
    • return x.constructor.toString().indexOf("Array") > -1, 调用toString方法, 如果是数组则会包含Array字段. 如:"function Array() { [native code] }".
    • instanceof操作符: arr instanceof Array.

会修改原数组值的有: pop, push, shift, unshift, reverse, splice, sort,

Map和Set(ES6语法)

Map初始化时传入一个二维数组,数组元素为两个元素的数组:前一个相当于key,后一个是value,可以通过key直接访问value。Set相当于只有key。
Map使用方法:set(key, value); get(key); has(key); delete(key)
Set:add(key) has(key) delete(key)

可迭代对象

ES6的语法:

for (var item of arr) {}

可迭代对象的forEach()方法是ES5.1的语法,使用方式如下:

arr.forEach(function(item, index, array) {})

传入一个回调函数,对每个元素执行一次回调函数。array指向对象本身。

对象
对象简述

简单来说就是用{}包含起来的键值对。function是对象的继承.

有个in可以判断对象是否有该属性。不过继承属性in判断也是为true。

'name' in student;

判断一个属性是否对象的非继承属性使用hasOwnProperty(key)
数组也是对象,可以把数组的索引看作对象的属性。
便利对象时可以用:

for(var index in obj) {
	console.log(obj[index]);
}
创建对象

直接定义:

var obj = {}

使用构造函数创建:

function Structure(){}
var obj = new Structrue()

这里必须用new来新建一个对象, 否则就是直接调用函数, 没有返回值. new的作用就相当于默认在函数中加了个return this. 注意: 构造函数尽量不要手动去return值

对象的继承

js的继承和大部分语言的继承方式有比较大的出入. js还没有class(类)这种说法(目前虽然出了新的Class关键字, 但目前兼容性还不是很好), 所谓的类就是一个实例对象.

继承方式有:

  1. 直接拷贝这个对象, 或者用子对象的__proto指向父对象. 低版本的IE无法使用__proto__.

  2. Object.create(father)father作为原型传递进去, 返回一个新的对象.

  3. 如果是用function定义类对象, 可以

原型链

instanceof运算符用于检验构造函数的prototype属性是否出现在对象的原型链中的任何位置,返回一个布尔值。

[] instanceof Array  // true	

主要的关键字: prototype, __proto__, constructor

prototype: 构造函数的一个属性, 指向一个实例对象. 就是说, 我们在创建这个构造器的时候系统自动帮我们创建了一个对应的对象, 我们可以通过prototype获取到这个对象. 这个对象好像叫实例原型(或原型对象?).

那么这个东西有什么用呢? 回想一下, 当我们创建对象的时候, 几乎是是复制了整个对象, 但是有很多函数都是公用的, 这就浪费了一些内存, 也不方便扩展. 我们可以把一些共有方法加入prototype, 当在对象上找不到这个方法时会通过__proto__获取原型对象.

ps: prototype添加的函数this指针指向调用对象.

constructor: 实例原型指向构造函数(好像用的场合不多)

__proto__: 上面的系统帮我们创建的一个实例对象除了通过类对象的prototype去访问外, 这个类所有子对象的__proto__属性都指向上面的实例原型.

(原型链就这???)

image-20200819185736760

Promise对象

用于实现JS的异步机制, 非常重要!!!

执行流程(无其他异步操作, 如定时器):

new Promise
执行传入的函数
遇到resolve
promise的状态由pending改为fulfilled
遇到reject
promise的状态由pending改为rejected
继续执行函数之后的语句,如再遇到reject/resolve则忽视
返回一个promise对象
调用then/catch
调用then/catch传入的函数,对应参数为上面resolve/reject函数传入的参数

用一段代码理解一下:

var p = new Promise(function(resolve, reject){
  console.log(1)
  resolve(2)
  resolve(3)
  reject(4)
  console.log(5)
})
console.log(6)
p.then(function(val){
  console.log('then',val)
}).catch(function(val){
  console.log('catch', val)
})
console.log(7)
  • 创建一个Promise对象的时候, 立即执行传入的function.
  • 输出1; 遇到resolve, pending状态改为fulfilled(已完成). 并保留参数2(后面then要用)
  • 之后遇到resolve和reject, 由于状态已经不是pending了, 直接忽略这两个函数.
  • 打印5. 整个函数执行完成, 返回一个Promise对象(的引用)给变量p.
  • 打印6. 遇到then/catch, 现在的状态是fulfilled, 所以执行then.(如果Promise状态为rejected, 执行catch的回调函数). 但不是立即执行, 而是把传入的function加到事件队列尾部(你需要先了解JS的事件队列).
  • 打印7. 主程序运行完毕, 然后是下一个事件, 就是then里面的这个函数执行. 函数接受一个参数, 就是上面resolve传入的参数2. 所以这里打印then 2.
  • 顺序输出为: 1, 5, 6, 7, then 2

除了then(onresolve).catch(onreject)的写法, 也可以不写catch, 把两个函数都作为then的参数, 即then(onresolve, onreject)的写法, 不过前者更直观一些, 推荐.

带有定时器的Promise

前面的是一个非常简单的例子. 如果我们在Promise传入的函数中设置了一个定时器, 延迟一定时间后在改变Promise的状态, 这样的话下面的then/catch都只能读取到pending状态? 这样会发生什么情况呢? 如下代码:

var p = new Promise(function(resolve, reject){
  console.log(1)
  //把修改状态的语句写入定时器.
  setTimeout(function(){
    resolve(2)
  },3000)
  console.log(5)
})
console.log(6)
p.then(function(val){
  console.log('then',val)
}).catch(function(val){
  console.log('catch', val)
})
console.log(7)

直接说结论, 输出的结果同上. 不过最后一个结果会等待三秒才输出.

当我们调用then/catch的时候, 如果当前的状态为pending, 则将对应的函数压入一个队列, 当改变Promise状态时, 再逐个执行这些函数.

下面来两段一个大佬自己实现的模拟Promise源码, 很觉得很有助于理解Promise的实现原理和运行机制.

原博客链接: https://www.jianshu.com/p/43de678e918a

思想还是很简单的:

  • 当我们在创建Promise传入的回调函数中, 如果遇到resolve/reject, 判断当前状态是否为pending, 不是pending则直接忽略.

  • 如果是pending, 则修改当前状态为fulfilled/rejected.

  • 看第二段代码, 当我们执行then函数的时候, 如果Promise的状态为pending, 把对应的执行函数压到队列里面. 如果为fulfilled/rejected, 则直接执行then参数里面的函数.

  • 再回到第一段代码, 就是上面then执行时遇到pending的情况, 当pending转化为fulfilled/rejected的时候, 调用相应方法, 查询执行函数队列, 并按顺序执行队列里面的函数. 这样就比较完美的实现了异步机制.

  • 对于下面的第二段代码, 有个地方没实现好, then方法应该是异步执行的, 这里当Promise已经是fulfilled时, 直接执行then传入的回调函数. 我觉得应该这样去实现: setTimeout(()=>{onFulfilled(_value)},0).

// 添加resovle时执行的函数
_resolve (val) {
  if (this._status !== PENDING) return
  // 依次执行成功队列中的函数,并清空队列
  const run = () => {
    this._status = FULFILLED
    this._value = val
    let cb;
    while (cb = this._fulfilledQueues.shift()) {
      cb(val)
    }
  }
  // 为了支持同步的Promise,这里采用异步调用
  setTimeout(() => run(), 0)
}
// 添加reject时执行的函数
_reject (err) { 
  if (this._status !== PENDING) return
  // 依次执行失败队列中的函数,并清空队列
  const run = () => {
    this._status = REJECTED
    this._value = err
    let cb;
    while (cb = this._rejectedQueues.shift()) {
      cb(err)
    }
  }
  // 为了支持同步的Promise,这里采用异步调用
  setTimeout(run, 0)
}

then的实现:

// 添加then方法
then (onFulfilled, onRejected) {
  const { _value, _status } = this
  switch (_status) {
    // 当状态为pending时,将then方法回调函数加入执行队列等待执行
    case PENDING:
      this._fulfilledQueues.push(onFulfilled)
      this._rejectedQueues.push(onRejected)
      break
    // 当状态已经改变时,立即执行对应的回调函数
    case FULFILLED:
      onFulfilled(_value)
      break
    case REJECTED:
      onRejected(_value)
      break
  }
  // 返回一个新的Promise对象
  return new MyPromise((onFulfilledNext, onRejectedNext) => {
  })
}

但是then地用法不止如此, 有时候你可能会看到一连串的then. 在上面代码也看到, then返回的是另一个Promise对象, 所以then之后可以继续接then的.返回的Promise对象的状态和参数是怎么样的呢? 源码我就不继续举了, 来个例子了解一下就好了.

var p = new Promise(function(resolve, reject){
  resolve(666)
})
.then(function(val){
  console.log('then',val)
  return 'hello'
}).then(function(val){
  console.log(val)
  return 'world'
}).then(function(val){
  console.log(val)
  return new Promise(function(resolve, reject){
    setTimeout(function(){
      reject('goodbye')
    },3000)
  })
}).catch(function(val){
  console.log(val)
  return 'see you again'
}).then(function(val){
  console.log(val)
})
console.log(999)

代码略长, 但是不难理解.

  • 创建一个Promise对象, 调用第一个then方法, 这个是和上面讲的列子是一样的. 不同的是, 第一个then返回一个hello字符串.
  • 调用第二个then, 我们可以看到它的回调函数传入的参数就是上一个then返回的内容. 说明调用then方法的时候Promise内部会帮我们创建一个新的Promise对象, 而且是调用了resolve并传入then返回的值(hello).
  • 然后到第三个then了, 也是输出上一个then的返回值. 但是这个then返回的是一个Promise对象. 直接返回Promise对象的话这个就直接把这个promise对象最为then的返回值, 相当于之后的then/catch都是这个Promise对象的调用.
  • 因为上一个then返回的是一个rejected状态的Promise对象, 所以找catch来调用. 在catch里面也是直接返回一个字符串.
  • 下一个调用到的是then而不是catch, 说明即使在catch里面返回的非promise数据, 系统默认新建的还是fulfilled状态的Promise对象(就是上面说的默认执行了resolve).

这一连串的then并不会阻塞主函数的运行, 所以输出的结果为: 999, then 666, hello, world, goodbye, see you again.

函数
定义与调用

js函数支持传入不定参数,而且使用非常方便。直接在在函数内使用arguments就可以得到传递的所有参数。

test = function() {
	for (var item of arguments) { console.log(item) }
}
test(1,2,3,'4')

arguments是一个对象,不是数组。可以通过for…of遍历,但是好像用不了forEach。

  • 如果不想使用arguments, 可以在参数后面加...rest来接收后面的参数。接受到的参数转化为数组形式,如果没有可选参数则为[]。

刚刚看到一个Array.from()方法,在这里先记一下。
三个参数,一个是类数组对象,如集合和前面的arguments;第二个是一个回调函数,传入类数组对象的每个元素,返回值作为新数组的元素;第三个为执行回调函数是this指向的对象(其实前面的应该也是这个说法)。

再多bb一个,浅拷贝和深拷贝。浅拷贝只有第一层数据不共享,其他的都共享;深拷贝完全不共享。
怎么说呢?比如[a,[b,c]],第一层是值a和[b,c]的引用(类似地址),而第二层是里面的b,c.。

拷贝方式第一层数据第二层以及之后的数据实现方案
直接赋值共享共享直接赋值
浅拷贝不共享共享Object.assign()
深拷贝不共享不共享_.cloneDeep()
两种定义函数的区别
var fun1 = function(){}
function fun2(){}

这两个都是函数定义的方式. 由于js的机制, 先扫面全局, 把变量的定义抽到最前面. 区别

  • 在定义前调用的话, 前者的fun当作普通变量来定义, 返回是undefine, 不可调用.

  • 而后者定义的时候就声明这个一个函数, 可以正常调用.

箭头函数和定义函数的区别

1、this指向不同;箭头函数的this指向自身, 而定义函数的this指向定义定义函数的对象 (有点套娃)

var fun = {
  f1 : (test)=>{ 
    this.a=0
    console.log(this)//指向f1
  },
  f2: function(test){
    console.log(this)//指向fun
  }
}

2、箭头函数不可以当作构造函数使用,也就是不能用new命令实例化一个对象,否则会抛出一个错误;

3、不可以使用arguments对象,该对象在函数体内不存在,如果要用的话,可以用rest参数代替;

4、不可以使用yield命令,箭头函数不能用作Generator函数;

变量作用域与结构赋值
  • 内部作用域屏蔽外部作用域
  • js会先扫描整个函数体的语句, 把所有变量和函数抽到顶部. 所以就算先调用后声明, 不会报错, 但是值为undefine.(就是说只是把声明抽到前面, 赋值操作仍未进行)
  • js有一个默认的全局变量window . 所以会有很多window.xxx的操作. 但是这个有时候直接在IDE里面是跑不通的, 因为是node环境, window是在web环境中使用.

let和const是es6新增关键字. 因为var的作用域是函数内部, 就算你在for或者if语句内用var定义了一个变量, 在整个函数内都会生效, 这不是我们想要的. let就是为了解决这个问题而诞生的. let在for块级作用域之外不会生效,所以下面的代码段会报错. 如果换成var仍然还会输出i, 这个不是我们想要的.

function(){
    for(let i=0;i<10;i++){}
    console.log(i)
}

es6还有一个新东西, 结构赋值. 简单书就是相同结构对应位置赋值.(有点像py那一套, 不过好像更高级一点).

[x,[y,z]] = [a,[b,c]]
[,,z] = [a,b,c]
{a,b} = {a:1; m:null; b:'2'}
this的用法以及大坑

用过和java的this, python 的self类似, js 也有一个用来指向对象内部的一个this指针. 通过this调用对象内部的一些变量.

坑:

  • 对象内部的函数赋值给外部的变量后不能调用(因为无法正确获取this.)
  • 对象内部函数定义的函数, this指针指向全局(strict模式下指向指向undefine).

改变this指向

通过上面了解到this会指向我们不想要的地方, 但我们还是有办法解决这个问题的.

apply() 和 call()都可以实现,没有太大区别. 用法:

func.apply(object,[x,y])
func.aplly(object, x, y)

这样调用func时this指针指向object. 和call不同的是, apply第二个参数传递一个数字, 包含func需要的参数; call直接传入相应的参数.

bind()也可以改变this的指向. 不过apply和call都直接执行相应的函数, 而bind返回一个函数.

var newFunc = oldFunc.bind(object, x)
newFunc(y,z)

newFunc的this指向传入的第一个参数, 并绑定后面的参数. 可以用来固定部分参数.

高级函数

这里只记一下回调高级函数传入的回调函数的用法.除了sort(), 原数组不变.

  • map: 回调函数传入一个参数, 返回值为数组新的值.

  • reduce: 回调函数传入两个值, 执行n-1次, 第一个参数在第一次执行回调函数时时数组第一个值, 之后都是前一次调用回调函数的返回值. 最终的返回值为最后一个回调函数的返回值.

  • filter: 回调函数传入一个参数, 返回值为True则加入新数组.

  • sort: sort会修改原数组. 回调函数传入两个值, 通过返回值确定顺序.

    根据返回-1的条件确定排序顺序. 这里返回-1的条件是x>y, 最后数组的排序顺序为由大到小.

arr.sort((x,y)=>{return x>y?-1:0})
关于浏览器的重定向

当前浏览器页面的网页替换成指定链接,并且无前进后退记录(适用于一些加载页面):

window.location.replace(url);

只是跳转到指定链接, 可后退返回原来的页面:

window.location.href="url"
练习过程中的一些问题
创建用function创建对象时发现无法内部元素

之前为了测试函数返回, 在后面加了个return语句, 导致创建对象时出错. 构造函数可以有return语句, 不过new时默认返回this, 返回其他可能用的时候会有问题.

还有一点, 函数定义时是function不是Function, 容易搞错.

function ofun() { 
  this.a=2
  this.tfun = function() {
    console.log(this.a)
  }
  // return this.tfun
}
var oo = new ofun()
console.log(oo.a)
oo.tfun()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值