javascript

快速入门

基本语法:会自动加“;”,“{}”可嵌套,注释//,区分大小写

  1. 数据类型和变量:

不区分整型和浮点型,统一用Number

字符串’'、“”

bool,与&&,或||,非!

比较运算==会自动转换数据再比较,===严格等于,不转换数据类型(用这个)

NaN:特殊的Number与所有其他值都不相等,包括它自己

浮点数的比较一般通过两者之差的绝对值是否小于某阈值

空值null,未定义undefined

数组索引起始值0

对象键是字符串类型,值可以是任意数据类型

变量:用var申明,可以反复赋值,可以把任何数据类型赋值给变量

启动strict模式,强制通过var声明变量:第一行加上“‘use strict’;”

3.字符串

模版字符串:“${变量}”

字符串不可变

toUpperCase(),toUpperCase()

indexOf()搜索指定字符串出现的位置

substring()返回指定索引区间的子串,substring(1,4)从1到4,不包括4

4.数组

可以包含任意数据类型,通过索引访问,允许越界访问,会修改数组大小

indexOf

slice(start,end)不包括end

5.对象

函数

定义

function name(x){

return xxx

}

var name=function (x){

return xxx

};

arguments 获得传入所有参数

rest获得部分参数

注意自动添加;的机制,return {

}

变量提升,会将声明的变量提升到函数顶部,需要在函数内部首先声明所有的变量

全局对象window

名字空间,把变量和函数绑定到一个全局变量中

let声明块级作用域的变量

常量,ES6,const

解构赋值同时对一组变量进行赋值

apply(),call()

map,reduce,filter,sort

every所有元素是否满足条件,find找到符合条件的第一个,findIndex找到符合条件的第一个的索引,forEach遍历数组

闭包,箭头函数

标准对象

1.包装对象,用new创建,不要轻易使用,尤其是string

2.Data对象月份值从0开始,使用Date.parse()时传入的字符串使用实际月份0112,转换为Date对象后getMonth()获取的月份值为011

3.\d匹配数组, \w匹配字母或数字,.匹配任何字符,*表示任意个字符(包括0个),+表示至少一个字符,?表示0个或1个字符,{n}表示n个字符,{n,m}表示n-m个字符,[] 表示范围,()表示要提取的分组

4.json序列化:把对象序列化成json格式的字符串,反序列化:把json反序列化成一个js对象

基础

1.原始类型:string,number,boolean,null,undefined,symbol

2.对象类型:object,除了原始类型,其他的都是对象类型,对象类型存储的是地址,原始类型存储的是值

3.typeof:能准确判断除null以外的原始类型的值,对于对象类型,函数会判断成function,其他的对象返回object

instanceof通过原型链可以判断出对象的类型,但并不是百分百准确

4.类型转换:

三种,转换成数字,布尔值,字符串

原始

转换目标

结果

number

布尔值

除了0、-0、NaN都为true

string

布尔值

除了空串都为true

undefined、null

布尔值

false

引用类型

布尔值

true

原始

转换目标

结果

number

字符串

5=>‘5’

布尔值、函数、symbol

字符串

‘true’

数组

字符串

[1,2]=>‘1,2’

对象

字符串

‘[object Object]’

原始

转换目标

结果

string

数字

‘5’=>5,‘a’=>NaN

数组

数字

空数组为0,存在一个元素且为数字时转为数字,其他情况NaN

null

数字

0

除了数组的引用类型

数字

NaN

symbol

数字

抛错

5.=

===严格相等,要求值和类型也想等

==不严格相等,只要值相等即可,会进行类型转换,规则如下:

类型相同比较值的大小

类型不同进行类型转换

判断是否是null、undefined,是的话返回true

判断的是String和Number,则把String类型转换成Number再比较

判断的其中一方是boolean,则把boolean转成number,再比较

其中一方是object,一方是string、number、symbol,则将object转成原始类型后,再比较

(1)转boolean:除了undefined、null、false、0、-0、NaN和空字符串转换成false以外,其他的值都转换成true,包括所有对象

6.new构造调用的过程

(1)创建一个新对象

(2)原型绑定

(3)绑定this到这个新对象上

(4)返回新对象

7.this

(1)独立函数调用,例如getUserInfo(),this指向全局对象window

(2)对象调用,对象调用自己的方法、属性stu.getName(),this指向调用的对象stu

(3)call(),apply(),bind()改变上下文的方法,this的指向取决于这些方法的第一个参数,当第一个参数为null时,this指向全局对象window

(4)箭头函数没有this,箭头函数里面的this只取决于包裹箭头函数的第一个普通函数的this

(5)new构造函数调用,this永远指向构造函数返回的实例上,优先级最高

8.闭包

在函数func1中定义了函数func2,内部函数func2可以引用外部函数func1的参数和局部变量,当func1返回函数func2时,相关参数和变量都保存在返回的函数中,当func2在func1外被调用时,就会形成闭包

表现形式:

(1)返回一个函数

(2)作为函数参数传递:无论通过何种手段将内部函数传递到它所在的语法作用域外,它都会持有对原始作用域的引用,无论在何处执行这个函数,都会产生闭包。

var a = 1;
function foo(){
var a = 2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
// 这就是闭包
fn();
}
// 输出2,而不是1
foo();

(3)回调函数:在定时器、事件监听、Ajax请求、跨窗口通信、Web Workers或者任何异步中,只要使用的回调函数,实际上就是在使用闭包。

(4)IIFE

9.浅拷贝深拷贝

拷贝结束后,对原对象中引用类型属性进行修改,副本会受影响,就是浅拷贝,否则是深拷贝

浅拷贝:

object.assign(),属性为对象,则拷贝这个对象的地址

…扩展运算符

深拷贝:

(1)使用JSON.parse()和JSON.stringify():会忽略属性值为undefined和属性为symbol的属性,不会序列化函数,不能解决循环引用的问题

(2)自己实现

function deepclone(obj) {
function isObject(o) {//判断是不是obj的函数方法
return typeof o === ‘object’ && o !== null
}
if (!isObject(obj)) {//先判断传进来的是不是对象,不是则报错,因为这是一个对象深拷贝函数
throw new Error(‘非对象’)
}
var newObj = Array.isArray(obj) ? […obj] : { …obj }//如果是数组就返回数组,这里返回的对象是具有新地址的对象
Reflect.ownKeys(newObj).forEach(key => {//对于newObj中的每个属性值,如果是对象的话则需要深拷贝替换一下,如果是值就不需要再深拷贝了
newObj[key] = isObject(newObj[key]) ? deepclone(newObj[key]) : newObj[key]//递归调用,深拷贝里面的对象,
})
return newObj
}

(3)使用lodash第三方库

23-07-17

10.继承

ES6之前,需要依赖原型、原型链和构造函数等技术手段组合使用,ES6后可以使用Class类继承(语法糖,本质还是函数)

几种方式:

(1)原型链实现继承:

通过重写子类的原型,并将它指向父类来实现。这样创建出来的实例即时子类的实例,又是父类的实例,缺陷:不能向父类构造函数传参,父类上的引用类型属性会被所有实例共享,其中一个实例改变时,会影响其他实例

function Animal() {
this.colors = [‘red’, ‘blue’];
}
function Dog(name) {
this.name = name;
}
Dog.prototype = new Animal();

(2)借用构造函数实现继承:

通过在子类中使用call()方法,实现借用父类构造函数并向父类构造函数传参的目的,但无法继承父类原型对象上的属性和方法

function Animal(name) {
this.name = name
this.colors = [‘red’, ‘blue’]
}
Animal.prototype.eat = function() {
console.log(this.name + ’ is eating’)
}
function Dog(name) {
Animal.call(this, name)
}

(3)组合继承:

组合原型链继承和借用构造函数继承两种方式,保留了两种继承方式的优点,但父类构造函数会被调用多次

function Animal(name) {
this.name = name
this.colors = [‘red’, ‘blue’]
}
Animal.prototype.eat = function() {
console.log(this.name + ’ is eatting’)
}
function Dog(name) {
Animal.call(this, name)
}

(4)寄生组合继承:

在组合继承的基础上,采用Object.create()来实现

function Animal(name) {
this.name = name
this.colors = [‘red’, ‘blue’]
}
Animal.prototype.eat = function() {
console.log(this.name + ’ is eatting’)
}
function Dog(name) {
Animal.call(this, name)
}
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog

(5)Class实现继承:

使用ES6 class新特性来实现继承

class Animal {
constructor(name) {
this.name = name
this.colors = [‘red’, ‘blue’]
}
eat() {
console.log(this.name + ’ is eatting’)
}
}
class Dog extends Animal {
constructor(name) {
super(name)
}
}

10.ES6

(1)var、let和const

var声明的变量会提升到作用域的顶部,而let和const不会进行提升

var声明的全局变量会被挂载到全局window对象上,而let和const不会

var可以重复声明同一个变量,而let和const不能

var声明的变量只要有范围是函数作用域,let和const声明的变量作用范围是块级作用域

const声明的变量,一旦声明则不能再次赋值,会报错(可以更改对象属性,对象地址不变即可)

(2)…扩展/收缩运算符

可以用于数组、对象扩展,函数参数的收缩

(3)解构赋值

数组和对象的解构赋值,允许按照一定的模式,从数组和对象中提取值,对变量进行赋值

(4)模版字符串

使用反引号,可以定义多行字符串,或者在字符串中嵌入变量、函数调用和表达式,写在${}中

(5)map和set

map结构:允许使用对象、数组等作为key

set结构:里面的值是唯一的,重复添加会被忽略(不允许强制类型转换,1和’1’是两个值),可以用于数组去重,计算并集,交集和差集

(6)proxy

可以一次性为所有属性实现代理,无需遍历,性能更佳

能监听到以前使用Object.definedProperty()监听不到的数据变动

是ES6新增的特性,使用浏览器兼容性方面比Object.definedProperty()差

(7)数组的map、filter和reduce的区别

(1)map:根据原数组的所有元素,(做一些改动),放进新数组中

(2)filter:从原数组中过滤出符合条件的元素,(进行改动),生成一个新数组

(3)reduce:通过回调函数的形式,把原数组中的元素最终转换成一个值,第一个参数是回调函数,第二个参数是初始值

var arr = [1,2,3,4,5,6];
var sum = arr.reduce((account, current) => {
return account + current;
}, 0);
console.log(sum); // 21

11.JS异步

(1)并发和并行

并行,使用多核cpu,能同时完成多个任务

并发,在一段时间内,通过任务切换完成多个任务

(2)回调函数

表现为事件绑定,Ajax请求或者其他的情况下,表现如下

ajax(url,()=>{
console.log(‘回调函数’)
})

(3)回调地狱

即回调函数嵌套过深。过长或者嵌套过深的回调函数,会使回调函数存在强耦合关系,一旦一个函数有所改动,则可能前一发而动全身。

(4)Generator

通过Generator,函数可以暂停自身,等到合适的机会再执行,可以解决回调地狱

generator由function*定义,除了return语句,还可以用yield返回多次

直接调用一个generator只是创建了一个对象,还没有去执行

调用generator对象有两个方法,一个是不断调用next()方法,会执行代码,遇到yield即返回一个对象{value:x,done:true/false},然后暂停。返回的value就是yield的返回值,done表示是否执行结束,若为true,则value就是return的返回值

第二种方法是直接用for … of循环迭代generator对象,不需要自己判断done

12.Promise

(1)promise一共有三种状态:pending(等待中)、resolve(完成)和reject(拒绝),在未来一定会有一个表决,并且只能表决一次,表决的状态一定是resolve或者reject。

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject,是两个函数,有js引擎提供,不用自己部署

const promise=new Promise((resolve,reject)=>{
if(/异步操作成功/res.code===200){
resolve(value)
}else{
reject(error)
}
})

resolve函数的作用是将promise对象的状态从“未完成”变成“成功”,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。

reject函数的作用是将promise对象的状态从“未完成”变成“失败”,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

(2)promise实例生成以后,可以用then方法分别制定resolved状态和rejected状态的回调函数

promise.then((value)=>{
return value//当执行resolve(value)后,promise状态变成resolved时,就会触发then绑定的这个回调函数
},(error)=>{
return error
})

then方法可以接受两个回调函数作为参数,第一个回调函数是promise对象的状态从resolved时调用,第二个回调函数是promise对象的状态从rejected时调用。第二个回调函数是可选的。

(3)如果调用resolve函数和reject函数时带有参数,那他们的参数会传递给then绑定的回调函数。resolve函数的参数除了正常的值以外,还可能是另一个promise实例。

(4)promise.resolve()

将对象转为Promise对象

Promise.resolve(‘foo’)
//等价于
new Promise(resolve=>resolve(‘foo’))

参数是个Promise实例,则不修改直接返回这个实例。

参数是个thenable对象(具有then方法的对象),会讲这个对象转为Promise对象,然后立即执行thenable对象的then方法。

参数不是具有then方法的对象,或者不是对象,是原始值,则返回一个新的Promise对象,状态为resolved,该参数会传给回调函数。

不带有参数时,直接返回一个resolved状态的Promise对象,但实在本轮“事件循环”(event loop)的结束时执行。

(5)promise.reject()

返回一个新的Promise实例,状态为rejected

const p = Promise.reject(‘error’)
//等价于
const p = new Promise((resolve,reject)=>reject(‘error’))

p.then(null,(s)=>{
console.log(s)
})

Promise.reject()方法的参数,会原封不动的作为reject的理由,变成后续方法的参数。

(6)Promise.prototype.then()

作用是为promise实例添加状态改变时的回调函数。

then方法返回的是一个新的promise实例,因此可以采用链式写法

(7)Promise.all()

把一个或者几个Promise组合在一个数组里面,只有当数组中的所有Promise全部表决完成,才返回。

接受一个数组作为参数,数组中的元素不是promise实例,则会先调用Promise.resolve()将参数转为promise实例,再进一步处理。

(8)Promise.race()

把一个或者几个Promise组合在一个数组里面,只要数组中有一个表决了,就返回

(9)async/await

如果一个方法前面加上了async,这个方法就会返回一个promise,async就是将函数用Promise.resolve()包裹了一下,并且await只能配合async使用,不能单独出现。

13.setInterval、setTimeout和requestAnimationFrame

(1)setTimeout延时执行某一段代码,但由于EventLoop的存在,并不百分百是准时的。

(2)setInterval在制定的时间内,重复执行一段代码,也不是百分百准时的,当与某些耗时的代码配合使用,会存在执行累计的问题,会等耗时结束以后,一起一个或者多个执行定时器。

(3)requestAnimationFrame请求动画帧,优势;

根据不同屏幕的刷新频率,自动调整执行回调函数的时机

当窗口处于未激活状态时,会停止执行,而setTimeout不会

自带函数节流功能

14.EventLoop事件循环

(1)进程和线程

JS是单线程执行的,在JS运行期间,有可能会阻塞UI渲染,这在一方面说明JS引擎线程和UI渲染线程是互斥的。用单线程的原因是,JS可以修改DOM,如果在JS工作期间,UI还在渲染的话,则可能不会正确渲染DOM。单线程的好处:节省内存空间,节省上下文切换时间,没有锁的问题存在。

进程:CPU在运行指令及加载和保存上下文所需的时间,应用上一个程序就是一个进程,一个浏览器tab选项卡就是一个进程

线程:是进程中的更小的单位,描述了执行一段指令所需的时间

(2)执行栈

存储函数调用的栈结构,遵循先进后出的原则

(3)EventLoop

当遇到异步代码时,会被刮起在Task队列中,一旦执行栈为空,就会从Task中拿出需要执行的代码执行。

异步任务可以分为:

宏任务(script,setTimeout,setInterval,setImmidiate,I/O,UI rendering)可以有多个队列

微任务(procress.nextTick,Promise.then,Object.observe,mutataionObserver)只能有一个队列

执行顺序:当执行栈执行完毕后,会首先执行微任务队列,当微任务队列执行完毕再从宏任务中读取并执行,当再次遇到微任务时,放入微任务队列

手写系列

1.给定一段URL和参数的名称,获取此参数的值

var url = ‘https://www.baidu.com/s?id=123&name=why&phone=13876769797’;
function getQueryString(name) {
var strs = ‘’;
var index = url.indexOf(‘?’);
if (index === -1) {
return undefined
}
strs = url.substring(index+1).split(‘&’);
for (let index = 0; index < strs.length; index++) {
var splitItem = strs[index].split(‘=’);
if(splitItem[0]==name) {
return splitItem[1];
}
}
};

// 测试:输出why
console.log(getQueryString(‘name’));

2.手写call、apply和bind方法

相同:

都是用来改变函数的this对象的指向的

第一个参数都是this要指向的对象

都可以利用后续参数传参

不同:

apply和call传入的参数列表形式不同,apply接收arguments(数组),call接受一串参数列表

bind语法和call一样,区别在于bind是等待执行,不兼容IE6-8,主要是将函数绑定到某个对象,bind()会创建一个函数,返回对应函数便于稍后调用,而apply、call是立即调用

总结:

基于Function.prototype上的apply、call和bind调用模式,可以显示的指定调用函数的this指向。apply接收参数的是数组,call接受参数列表,bind方法传入一个对象,返回一个this绑定了传入对象的新函数,这个函数的this指向除了使用new时会被改变,其他情况下都不会改变。若为空,默认是指向全局对象window。

call

Function.prototype.myCall = function (context) {
if (typeof this !== ‘function’) {
throw new TypeError(‘not a function’)
}
const symbolFn = Symbol()
const args = […arguments].slice(1)
context = context || window
context[symbolFn] = this
const result = contextsymbolFn
delete context[symbolFn]
return result
}
const obj = {
name: ‘obj’
}
function foo () {
console.log(this.name)
}
foo.myCall(obj) // obj

apply

Function.prototype.myApply = function(context) {
if(typeof this !== ‘function’) {
throw new TypeError(‘error’);
}
context = context || window;
context.fn = this;
var result = arguments[1] ? context.fn(…arguments[1]) : context.fn();
delete context.fn;
return result;
}
function foo(){
console.log(this.age);
}
var obj = {
age: 101
}
foo.myApply(obj); // 输出101

bind

Function.prototype.myBind = function(context) {
if(typeof this !== ‘function’) {
throw TypeError(‘error’);
}
const self = this;
const args = […arguments].slice(1);
return function F() {
if(this instanceof F) {
return new self(…args, …arguments);
}
return self.apply(context, args.concat(…arguments));
}
}
function foo() {
console.log(this.age);
}
var obj = {
age: 121
}
var newFunc = foo.myBind(obj);
newFunc(); // 输出121

3.写一个函数来判断变量类型

function getType(data){
let type=typeof data;
if(type!==“object”){
return type
}
return Object.prototype.toString.call(data)

4.深拷贝递归

function deepClone(obj){
function isObject(o){
return typeof o === ‘object’ && o !== null;
}
if(!isObject(obj)){
throw new Error(‘非对象’)
}
var isArray = Array.isArray(obj);
var newObj = isArray ? […obj] : {…obj}
Reflect.ownKeys(newObj).forEach(key => {
newObj[key] = isObject(newObj[key]) ? deepClone(newObj[key]) : newObj[key];
})
return newObj;
}

5.执行顺序

const first = () => (new Promise((resolve, reject) => {
console.log(3); //1
let p = new Promise((resolve, reject) => {
console.log(7); //2
setTimeout(() => {
console.log(1);//7
}, 0);
setTimeout(() => {
console.log(2);//8
resolve(3);
}, 0)
resolve(4); //4
});
resolve(2);
p.then((arg) => {// 5
console.log(arg, 5);
});
setTimeout(() => {
console.log(6);// 9
}, 0);
}))
first().then((arg) => {
console.log(arg, 7); //6
setTimeout(() => {
console.log(8);//11
}, 0);
});
setTimeout(() => {
console.log(9);//10
}, 0);
console.log(10); //3

你可能感兴趣的:(javascript)