值和引用
值在栈中保存 上->下
引用在堆中保存 下->上
深拷贝
问题:一个对象复制为另一个对象的时候,由于存放在堆区(具体再看),因此改变复制对象后原对象也会发生变换,因此需要深拷贝。
思路:
1.首先判断该变量类型是否为对象,不是则直接return
2.然后判断该变量类型为数组还是对象,创建相应的空的变量。
3.循环遍历一下对象的内容,如果都是他的本身属性(非原型属性),那么就递归调用该函数实现深拷贝。由于到末端的值都不为对象,因此在递归的时候会return出去然后赋值给上级的key,完成深拷贝。
ES6新增浅拷贝语法糖Object.assign(target,...sources)
const obj1={
name:'张三',
age:18,
address:{
city:'上海'
},
arr:['a','b','c']
}
const obj2=deepClone(obj1)
obj2.address.city="北京"
console.log('obj1是'+obj1.address.city, '')
console.log('obj2是'+obj2.address.city, '')
function deepClone(obj) {
if(typeof obj !=='object'||obj==null){
return obj
}
let res
// 判断是否为数组
if(obj instanceof Array){
res=[]
}else{
res ={}
}
for(let key in obj){
// 保证key不是原型的属性
if(obj.hasOwnProperty(key)){
// 递归调用 防止深层次的东西没有复制过来
res[key]=deepClone(obj[key])
}
}
return res
}
另一种写法
const obj1={
name:'张三',
age:18,
address:{
city:'上海'
},
arr:['a','b','c']
}
function deepCopy(newObj,oldObj){
// 循环遍历老对象赋值给新对象
for(var k in oldObj){
// 获取老对象的值
var item=oldObj[k]
if(item instanceof Array){
newObj[k]=[];
return deepCopy(newObj[k],item)
}else if(item instanceof Object){
newObj[k]={};
return deepCopy(newObj[k],item)
}else{
newObj[k]=item
}
}
}
var o={}
deepCopy(o,obj1)
// console.log(o);
字符串拼接
问题
- typeof能判断哪些类型
值类型 函数 引用 - 何时使用 === 以及 ==
除了null之外 - 值类型与引用类型的区别
- 深拷贝
原型
每个构造函数都有一个原型对象,来存放所有对象所共享的方法。
一般的,公共属性会用构造函数定义,而公共方法却用函数的原型对象存放。
function person(name,age){
this.name=name;
this.age=age;
// this.sing=function() {
// console.log(name+'我会唱歌');
// }
}
// 原型对象
person.prototype.sing=(name)=>console.log(name+'我会唱歌');
var zys=new person('张三','18');
console.log(zys);
zys.sing(zys.name);
继承
- extend 继承父类
- super 执行父类构造
- instanceof为类型判断
每个构造函数/class(ES5没有类的概念)都有显式原型 prototype
每个实例都有隐式原型__ proto __,指向prototype
隐式原型指向显式原型,隐式找不到就去显式去找
(原型存放方法)
__ proto __与prototype都有一个constructor属性,指回构造函数本身。
原型对象的this指向:无论是对象还是构造函数的this都指向被调用的实例对象。
// 利用原型对象扩展内置对象方法
Array.prototype.sum=function(){
var sum=0;
for(var i=0;i<this.length;i++){
sum+=this[i];
}
return sum;
}
var arr=[1,2,3];
console.log(arr.sum(), '求和');
console.log(Array.prototype)
原型链
构造函数.prototype===obj.__ proto __
父.prototype===子.prototype.__ proto __
hasOwnProperty() //验证是否为自身属性
问题
- 判断一个变量是否为数组
instanceof Array - 手写简易的jquery
没搞懂
class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector)
const length = result.length
for (let i = 0; i < length; i++) {
this[i] = result[i]
}
this.length = length
this.selector = selector
}
get(index) {
return this[index]
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const elem = this[i]
fn(elem)
}
}
on(type, fn) {
return this.each(elem => {
elem.addEventListener(type, fn, false)
})
}
// 扩展很多 DOM API
}
// 插件
jQuery.prototype.dialog = function (info) {
alert(info)
}
// “造轮子”
class myJQuery extends jQuery {
constructor(selector) {
super(selector)
}
// 扩展自己的方法
addClass(className) {
}
style(data) {
}
}
// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))
3.class原型本质
…
作用域
- 函数内部可以使用全局变量
- 函数外部不能使用局部变量
- 执行完毕后变量被销毁
闭包
闭包是指有权访问另一个函数作用域中的变量的函数。
被访问的变量所在函数就是闭包函数。
作为参数被传入以及作为返回值返回,返回值返回可以通过闭包在全局调用局部变量,延伸了变量的作用范围。
案例1.
// 点击li输出li的索引
// 1.利用动态添加属性的方式
var lis=document.querySelector('.nav').querySelectorAll('li')
for(var i=0;i<lis.length;i++){
lis[i].index=i;
lis[i].onclick=function(){
console.log(this.index);
}
}
// 2.利用闭包的方式 不点的话就不会销毁,容易内存泄漏
for(var i=0;i<lis.length;i++){
(function(i) {
// console.log(i);
lis[i].onclick=function(){
console.log(i);
}
})(i);
}
案例2.
var lis=document.querySelector('.nav').querySelectorAll('li')
// 打印所有li内容
for(var i=0;i<lis.length;i++){
(function(i){
setTimeout(() => {
console.log(lis[i].innerHTML);
}, 2000);
})(i);
}
自由变量的查找,是在函数定义的地方,向上级作用域查找,而不是在执行的地方。
this
取值是在执行时确认,而不是定义
应用场景:
- 普通函数调用
- .call apply bind
- call()、apply()、bind() 都是用来重定义 this 这个对象的,可以改变this的指向
- 区别:
1. call的参数后边挨着放。call(obj,params1,params2)。
用途:可以实现继承。
2. apply的参数第二个在数组中。call(obj,[‘params1’,‘params2’])。
用途:将传递的参数数组转化为相应类型,可对数组进行操作。求最大值。
3. bind的参数同第一个但是后边得加()到新的函数。call(obj,params1,params2)()
用途:不立即调用。禁用按钮,三秒后开启。
// call实现继承属性
function Father(name,age){
this.name=name;
this.age=age;
}
function Son(name,age,score) {
Father.call(this,name,age);
this.score=score;
}
var son=new Son('张三',18)
console.log(son)
// call实现继承方法
function Father(name,age){
this.name=name;
this.age=age;
}
Father.prototype.money=function(){
console.log(100000);
}
function Son(name,age,score) {
Father.call(this,name,age);
this.score=score;
}
// Son.prototype=Father.prototype //不能这么做,会把子方法赋给父方法
Son.prototype=new Father();
// 利用对象的形式修改了原型对象,别忘了利用constructor来指向原来了构造函数
Son.prototype.constructor=Son;
Son.prototype.exam=function(){
console.log(100);
}
var son=new Son('张三',18)
console.log(son)
// apply求最大值
var arr=[1,2,4,5,1,2,3,6];
var max=Math.max.apply(Math,arr);
console.log(max);
// bind禁用按钮
var btn=document.querySelector('button');
btn.onclick=function(){
this.disabled=true;
var that=this;
setTimeout(function(){
// 定时器的this指向window
// this.disabled=false;
// 此时this是bind绑定的btn对象
this.disabled=false;
// 可以这么做
// that.disabled=false;
// 这么做最好
}.bind(this),3000)
}
JavaScript 中 call()、apply()、bind() 的用法
- .对象方法调用
- class的方法中调用
- 箭头函数(指向上一级的this)
问题
- this在不同场景下如何取值
- 手写bind函数
函数内的this指向
调用方式 | this指向 |
---|---|
普通函数调用 | window |
构造函数调用 | 实例对象,原型对象方法也指向实例对象 |
对象方法调用 | 方法所属对象 |
事件绑定方法 | 绑定事件对象 |
定时器函数 | window |
立即执行函数 | window |
// 模拟 bind
Function.prototype.bind1 = function () {
// 将参数拆解为数组
const args = Array.prototype.slice.call(arguments)
// 获取 this(数组第一项)
const t = args.shift()
// fn1.bind(...) 中的 fn1
const self = this
// 返回一个函数
return function () {
return self.apply(t, args)
}
}
- 闭包应用场景
隐藏数据,只提供API。比如一个类的数据不能被外界修改 - 作用域和自由变量
- 闭包
- this
继承
原型链继承
- 让新实例的原型等于父类的实例。
构造函数继承
- 用.call()和.apply()将父类构造函数引入子类函数
组合继承(组合原型链继承和借用构造函数继承)(常用)
寄生式继承
寄生组合式继承
class继承-extends
单线程
js是单线程语言
js与DOM渲染之能用一个线程
异步
不会阻塞代码执行
使用场景
- 网络请求
- .定时任务
回调地狱callback hell
ajax异步调用接口不会顺序执行
$.ajax({
url:'http://localhost:3000/data',
success:function(data){
console.log(data)
}
})
$.ajax({
url:'http://localhost:3000/data1',
success:function(data){
console.log(data)
}
})
$.ajax({
url:'http://localhost:3000/data2',
success:function(data){
console.log(data)
}
})
解决方式为
$.ajax({
url:'http://localhost:3000/data',
success:function(data){
console.log(data)
$.ajax({
url:'http://localhost:3000/data1',
success:function(data){
console.log(data)
$.ajax({
url:'http://localhost:3000/data2',
success:function(data){
console.log(data)
}
})
}
})
}
})
此方式容易容易造成回调地狱
问题
1.同步和异步的区别
2.手写promise加载图片
function loadImg(src) {
const p = new Promise(
(resolve, reject) => {
const img = document.createElement('img')
img.onload = () => {
resolve(img)
}
img.onerror = () => {
const err = new Error(`图片加载失败 ${src}`)
reject(err)
}
img.src = src
}
)
return p
}
const url1 = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
const url2 = 'https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg'
loadImg(url1).then(img1 => {
console.log(img1.width)
return img1 // 普通对象
}).then(img1 => {
console.log(img1.height)
return loadImg(url2) // promise 实例
}).then(img2 => {
console.log(img2.width)
return img2
}).then(img2 => {
console.log(img2.height)
}).catch(ex => console.error(ex))
3.异步使用场景
网络请求
定时任务
Promise
promise为对象,它可以获取异步操作的消息。
promise实例
// 实例promise
var p=new Promise(function(resolve,reject){
// 成功时调用resolve()
// 失败时调用reject()
setTimeout(() => {
var flag=true;
if(flag){
// 正常情况
resolve('hello')
}else{
reject('出错了')
}
}, 2000);
})
p.then(function(data){
console.log(data);
},function(info){
console.log(info);
})
基于promise方式处理ajax调用接口
function queryData(url){
var p=new Promise(function(resolve,reject){
var xhr= new XMLHttpRequest();
xhr.onreadystatechange=function(){
if(xhr.readyState!=4)return;
if(xhr.readyState==4&&xhr.status==200){
//处理正常的情况
resolve(xhr.responseText)
}else{
reject('服务器错误')
}
}
xhr.open('get',url)
xhr.send(null)
})
return p;
}
queryData('http://localhost:3000/data')
.then(function(data){
console.log(data);
},function(info){
console.log(info);
})
多次ajax请求
// 发送多个ajax请求且保证顺序
queryData('http://localhost:3000/data')
.then(function(data){
console.log(data);
return queryData('http://localhost:3000/data1')
})
.then(function(data){
console.log(data);
return queryData('http://localhost:3000/data2')
})
.then(function(data){
console.log(data);
})
接口
const express=require('express')
const app=express()
const cors=require('cors')
app.use(cors())
app.get('/data',(req,res)=>{
res.send('hello world1')
})
app.get('/data1',(req,res)=>{
res.send('hello world2')
})
app.get('/data2',(req,res)=>{
res.send('hello world3')
})
app.listen(3000,()=>{
console.log('welcome to http://localhost/3000')
})
then参数的打印结果(上一个实例的返回值获得的结果)
- 返回上一个对象的结果
- 返回上一个对象的字符串,默认的promise
Promise常用API
- 实例方法
操作 | 说明 |
---|---|
.then() | 得到异步任务的正确结果 |
.catch() | 获取异常信息 |
.finally() | 成功与否都会执行 |
function foo(){
return new Promise(function(resolve,reject){
setTimeout(() => {
// resolve(123)
reject('错误')
}, 1000);
})
}
foo(function(data){
console.log(data);
}).then(function(data){
console.log(data);
}).catch(function(data){
console.log(data);
}).finally(function(){
console.log('finished');
})
- 对象方法
操作 | 说明 |
---|---|
.all() | 得并发执行所有异步任务,执行完成才得到结果 |
.race() | 得并发执行所有异步任务,只要有一个完成就能得到结果 |
var p1=queryData('http://localhost:3000/a1')
var p2=queryData('http://localhost:3000/a2')
var p3=queryData('http://localhost:3000/a3')
Promise.all([p1,p2,p3]).then(function(result){
// 得到全部结果
console.log(result);
})
Promise.race([p1,p2,p3]).then(function(result){
// 只得到一个结果
console.log(result);
})