README.md
可以下载到typora
中打开,会有整个大纲目录显示(github中markdown目录快捷生成方式不现实,之后可能会想办法生成贴过来,暂时不做相关处理)README.md
中会实时更新进度内容。gitbook中考虑整个学完整理完成之后,再去统一处理发布,敬请期待!gitbook
版本可建议后期碎片化时间进行复习使用。CSDN
博客专栏前端自我修养进阶中,也会一篇一篇实时更新相关知识点。标签语义化
音视频
webGL/canvas等动画渲染
websocket:
盒子模型
box-sizing:border-box
水平居中的实现方式
.container{
position:relative;
}
.box{
width:200px;
height:200px;
position:absolute;
left:50%;
top:50%;
margin-left:-100px;
margin-right:-100px;
}
//或者
.box{
position:absolute;
left:50%;
top:50%;
transformX:-50%;
transformY:-50%;
}
.container{
display:flex;
justify-content:center;
align-item:center;
}
.container{
display:table-ceil;
text-align:center;
vertical-align:middle;
width:500px;
height:500px;
}
.box{
display:inline-block;
}
响应式布局
.container{
display:flex;
flex-direction:row;
justify-content:space-between;
}
.left{
height:200px;
flex:0 0 200px; //缩放比0 0,宽度200px
}
.center{
flex:1; //或者使用flex-grow
}
.right{
height:200px;
flex:0 0 200px;
}
//方式一
.box a{
}
//方式二
a{
}
方式二:因为浏览器的渲染机制使从右到左进行查找的
let a = {
},b = '0',c = 0;
a[b] = '张三'
a[c] = '李四'
console.log(a[b]) //李四
对象中,数字属性名和字符串属性名都是一样的,数组是特殊的字符串
拓展:对象和数组的区别
let a={
}, b=Symbol('1'), c=Symbol('1');
a[b]='张三';
a[c]='李四';
console.log(a[b]); //张三
Symbol创建的是唯一的值,不相等。对象的属性名不一定是字符串,如果是数字和字符串则是同一个值,因为索引是字符串。对象的属性名可以是不二null,Symbol、undefined等等。引用类型值都是变成字符串逆行存储。
拓展:自己实现一个Symbol
let a={
}, b={
n:'1'}, c={
m:'2'};
a[b]='张三';
a[c]='李四';
console.log(a[b]); //李四
b、c作为引用的时候会被转化成为字符串,对象转化toString那么即
[Object Object]
,两个都是[Object Object]
,所以结果是李四
拓展:Object.prototype.toString项目中应用,跟valueOf跟toString区别(编译顺序)
基本类型直接存储,引用类型要放进堆里面,最终是把地址复制给这个值。
浏览一加载页面,就形成一个栈内存。每个函数执行,叫做把一个执行上下文压缩到栈中执行。
null和undefined
//立即执行函数,执行完被销毁,回收机制。但是由于i被占用因此不销毁。test表示的不是一个函数,而是一个函数返回的执行结果
var test = (function(i){
return function(){
alert(i*=2); //没有i,那么在它上级作用域中找。在哪创建的上级作用域就是谁
}
})(2);
test(5); //字符串4。test中没有形参。
alert输出的结果都是会转化成字符串的。
var a=0,
b=0;
function A(a){
A=function(b){
alert(a+b++);
};
alert(a++);
}
A(1); //"1"
A(2); //"4"
注意: a++先跟别人运算,再自身累加1;++a先自身累加1,再跟别人运算。
过程解释:
1、GO全局:初始化 a = 0,b = 0, A一个方法,引用地址,此处暂时记为AAAFFF000
2、A(1)执行,传入形参,此时在执行中a= 1,往下中性,A = function…,此处相当于重新定义了全局的方法A,改变了原来的方法A的指向,此处引用地址从AAAFFF000改变到另外一个记为BBBFFF000,继续执行alert(a++),由于a=1,并且由于下面demo可以看出,a是在执行之后再自身加1的,那么此时alert的是没有叠加之前的1
3、继续执行A(2),那么此时A指向的function是改变之后的BBBFFF000,此时传入的形参b=2,而a在其上一级作用域中,由步骤2可知,叠加之后为2,那么此时alert出来的应该是2+2,为字符串4(alert出来的会自动同toString转化)
let a = 1;
console.log(5+a++); //6
console.log(a) //2
let b = 1;
console.log(5+(++a)); //7
console.loog(a) //2
闭包的作用:保存、保护
let obj = {
a: 100,
b: [10, 20, 30],
c: {
x: 10
},
d: /^\d+$/
};
let arr = [10, [100, 200], {
x: 10,
y: 20
}];
//=>深克隆
function deepClone(obj) {
if (typeof obj !== "object") return obj;
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Date) return new Date(obj);
let cloneObj = new obj.constructor;
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key]);
}
}
return cloneObj;
}
let obj = {
a: 100,
b: [10, 20, 30],
c: {
x: 10
},
d: /^\d+$/
};
let obj2 = {
}
for(let key in obj){
//obj.hasOwnProperty(key),true是私有的,false不是私有的。不是私有的不遍历,只遍历私有的
if(!obj.hasOwnProperty(key))break;
obj2[key] = obj[key]
}
//ES6实现浅克隆
let obj3 = {
...obj}
// JSON.stringify&JSON.parse
let obj = {
a: 100,
b: [10, 20, 30],
c: {
x: 10
},
d: /^\d+$/
};
let obj2 = JSON.stringify(obj); //"{"a":100,"b":[10,20,30],"c":{"x":10},"d":{}}"
obj2 = JSON.parse(obj2)
JSON.parse
这种方式弊端:正则、函数、日期、symbol等,会有问题
//递归实现深克隆
function deepClone(obj){
//或者let newobj = new obj.constructor;或者{
};一般obj.constructor为Object,防止传入的是实例
//不直接创建控对象的目的,克隆的结果和之前保持相同的所属类
//过滤特殊情况,object才递归
if(typeof obj === null)return null;
if(typeof obj !== "object")return obj;
if(obj instanceOf RegExp){
return new RegExp(obj)
}
if(obj instanceOf Date){
return new Date(obj)
}
let newobj = new Object();
for(let key in obj){
if(obj.hasOwnProperty(key)){
//判断是私有属性
newobj[key] = deepclone(obj[key ])
}
}
return newobj;
}
排除:null,Date,正则RegExp,以及不是object
function Foo() {
getName = function () {
console.log(1);
};
return this;
}
Foo.getName = function () {
console.log(2);
};
Foo.prototype.getName = function () {
console.log(3);
};
var getName = function () {
console.log(4);
};
function getName() {
console.log(5);
}
Foo.getName(); //2
getName(); //4
Foo().getName(); //Foo()普通函数执行;Foo执行过程中,内部函数有个getName赋值,但是getName并不是私有的,于是此时重新定义全局的getName = -> 1,并且返回this,这里的this的指向就是window,那么应该是window.getName 1
getName(); //1
new Foo.getName(); //无参数new,点的方式叫做成员访问。优先级问题,先执行成员访问,new一个输出2的函数,那么输出也是2
new Foo().getName(); //有参数new,执行方式先new Foo,再getName。创建实例,实例的getName,那么应该找prototype上的,因此结果是3
new new Foo().getName(); //优先级new (new Foo()).getName().getName,先new Foo()创建一个实例foo,变成 new foo.getName(),此时变成了先成员访问,原型上的方法,输出3的方法,变成了new 3,即3
注意:由参数的new先执行new,无参数的new和成员访问两个同级别,谁先救先执行谁
答案为:2 4 1 1 2 3 3
//function声明加赋值,var先声明先不赋值
Foo AAAFFF0000
getName = func -> 5
//代码执行
Foo是一个堆,里面存了方法代码字符串,Foo仍然是一个对象,有prototype(也是一个引用类型即也是一个堆)、length、然后我们给他加了一个getName属性;
getName = func -> 4 //之前赋值输出5,之后重新赋值输出4的
变量提升:在var function,所有代码执行之前,带var和function提前定义和赋值
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
浏览器多线程,js单线程。时间队列EventQueue。微任务队列,宏任务队列。主线程代码先执行。
定时器、事件绑定、ajax都是宏任务,async、await、promise等是微任务
function A(){
alert(1);
}
function Func() {
A=function(){
alert(2);
};
return this;
}
Func.A=A;
Func.prototype={
A:()=>{
alert(3);
}
};
A();
Fn.A();
Fn().A();
new Fn.A();
new Fn().A();
new new Fn().A();
//注意最后一个,箭头函数是不可以被new的,因为箭头函数没有原型链,也就没有constructor构造器
var a = ?;
if (a == 1 && a == 2 && a == 3) {
console.log(1);
}
两个等于号转化数据类型值相等,三个等于好要绝对相等
双等
- 对象==字符串 对象toString变成字符串,再变成数字,对比;[10] == 10
- null == undefined但是和其他比较不想等
- NaN和任何东西都不想等
- 剩下的都转化成数字。eg.
'1'==true
1、利用toString
由于两个等号对比,一方为数字,我们假设a为object(例如:[1],[2],[3]这样的,数组是特殊的object),那么他们执行的对比方式一定是要先toString再转化成数字进行对比的。因此我们可以在toString上做文章,去修改它的toString
var a = {
i:0,
toString(){
return ++this.i;
}
}
//因此三次对比,执行三次toString,依次返回1,2,3。valueOf也可以代替toString
var a= [1,2,3]
a.toString = a.shift; //shift方法删除数组第一个值,并且返回第一个数值。因此每次对比要调用a的toString的时候,依次获得的数值就是i,2,3
2、利用数据劫持get:
由于a是全局的,那么再全局上去劫持获取a的set方法去修改
var i = 0;
Object.definedProperty(window,'a',{
set(){
return ++i;
}
})
3、或者用true也行
react和vue区别
vue中数据的双向绑定2.0和3.0的实现
Object.definedProperty
进行set和get修改;Proxy
进行拦截set和get方法进行相关操作,实现数据的双向绑定。1、JSONP
利用script标签
- 只有get请求
- 不安全
- 有缓存
- 传递的数据大小有限制
- 需要服务器单独的支持
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
$.ajax({
url: 'http://127.0.0.1:8001/list',
method: 'get',
dataType: 'jsonp',
success: res => {
console.log(res);
}
});
</script>
let express = require('express'),
app = express();
app.listen(8001, _ => {
console.log('OK!');
});
app.get('/list', (req, res) => {
let {
callback = Function.prototype
} = req.query;
let data = {
code: 0,
message: '测试数据'
};
res.send(`${
callback}(${
JSON.stringify(data)})`);
});
2、基于iframe的跨域解决方案
主域相同,子域不一样
3、CORS跨域资源共享
客户端
import axios from 'axios';
import qs from 'qs';
axios.defaults.baseURL = "http://127.0.0.1:3000";
axios.defaults.timeout = 10000;
axios.defaults.withCredentials = true;
/*
* 设置请求传递数据的格式(看服务器要求什么格式)
* x-www-form-urlencoded
*/
axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded';
axios.defaults.transformRequest = data => qs.stringify(data);
/*
* 设置请求拦截器
* TOKEN校验(JWT):接收服务器返回的token,存储到vuex/本地存储中,每一次向服务器发请求,我们应该把token带上
*/
axios.interceptors.request.use(config => {
let token = localStorage.getItem('token');
token && (config.headers.Authorization = token);
return config;
}, error => {
return Promise.reject(error);
});
/*
* 响应拦截器
*/
axios.interceptors.response.use(response => {
return response.data;
}, error => {
});
export default axios;
服务器
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "";
res.header("Access-Control-Allow-Credentials", true);
res.header("Access-Control-Allow-Headers", "PUT,POST,GET,DELETE,OPTIONS,HEAD");
res.header("Access-Control-Allow-Methods", "Content-Type,Content-Length,Authorization, Accept,X-Requested-With");
req.method === 'OPTIONS' ? res.send('CURRENT SERVICES SUPPORT CROSS DOMAIN REQUESTS!') : next();
});
4、基于http proxy实现跨域请求
开发时候用proxy,部署用nginx反向代理
vue
react
组件通信有哪些方案
属性方案:父传子
vue发布订阅(子传父)
React传回调函数(子传父)
祖先和后代进行通信:后代元素中的所有元素都放到祖先中,叫做上下文
session和cookie的区别
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
//转化成Set
//转化成Array方式一
let arr = [...new Set(ary)];
//转化成Array方式二
let arr = Array.from(new Set(ary))
遍历对比
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
let arr = [];
for(var i;i<ary.length;i++){
let item = ary[i]
let args = ary.slice(i+1) //后面的值一一对比
if(args.indexOf(item)>-1){
}else{
arr.push(item)
}
}
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
let arr = [];
for(var i;i<ary.length-1;i++){
//最后一个不用对比,肯定与前面都不想等
let item = ary[i]
let args = ary.slice(i+1) //后面的值一一对比
if(args.indexOf(item)>-1){
arr[i] = null; //每一个与后面的进行对比,如果是有相同的就设置成null
}
}
ary = ary.filter(item=>item!==null) //筛去等于null的
拿数组中的每一项,往新容器中存储,如果没有就存,有就不存
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
let obj = {
}
for(var i;i<ary.length;i++){
let item = ary[i];
//如果对象没有这个属性就是undefined;也可以用in或者Object.keys。不是
if(typeof obj[item] !== 'undefined'){
//一定有了这个属性
//把最后一项拿过来替换。长度减1,i减1
ary[i] = ary[ary.length - 1]
ary.length--;
i--;
continue;
}
obj[item] = item
}
obj = {
}
ary.sort(); //升序
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
let arr = [...new Set(ary)] //使用展开运算符
let arr = Array.from(new Set(ary)) //使用Array.from
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
for(let i = 0; i < ary.length - 1;i++){
let item = ary[i],
args = ary.slice(i+1); //取出除当前项之后的所有项为一个数组
if(args.indexOf(item)>-1){
//判断当前项是否在其后面数组项中存在;或者此处可以使用includes
//此处有两个思路:1、包含,将当前项删除 2、定义一个新数组将当前的不包含的放到新数组中,这样的话要遍历所有的包括最后一项
//删除方式一:splice(i,1);i--; //i--是为了解决删除后导致的数组塌陷问题
//使用splice删除会造成问题 1、原来数组改变,如果继续i++会造成数组塌陷问题 2、性能不好,一旦当前项删除,后面每一项的索引都要改变
//删除方式二:原来数组克隆一份一模一样的,之后在克隆数组中去删除
//删除方式三:赋值为null,之后过滤掉ary.filter(item=>item !== null)
// ary[i] = null;
//删除方式四:用最后一项替换,替换之后整个数组长度要减少1,并且替换过来后当前的替换项需要重新对比所以i--
ary[i] = ary[ary.length - 1]
ary.length--;
i--;
}
}
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
let obj = {
};
for(let i = 0;i<ary.length;i++){
let item = ary[i];
if(typeof obj[item] !== 'undefined'){
//对象中有这个属性,不存储
//此处对数组的操作跟上面的方式对数组的操作一样,都是讲最后一项替换到当前项
ary[i] = ary[ary.length - 1]
ary.length--;
i--;
continue;
}
obj[item] = item;
}
obj=null; //obj使用完之后,销毁掉当前使用的堆
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
ary.sort((a,b)=>a-b) //升序排序
//方式一:每一项和后一项进行比较,如果相同就删除当前项。最后一项没有后一项,所以不用
for(let i = 0;i<ary.length-1;i++){
if(ary[i] === ary[i+1]){
ary.splice(i,1);
ary.length--;
i--;
}
}
//方式二:可以使用正则
ary = ary.join('@')+'@' //"12@12@14@15@16@23@23@25@25@"
let reg = /\d+@\1*/g // \d拿到数字。 \d+@ 拿到数字加上@符号
let arr = []
ary.replace(reg,(val,group1)=>{
arr.push(Number(group1.slice(0,group1.length-1) ))
})
conole.log(arr)
数组去重经典的四大方案:
使用ES6的Set方案
前一项跟后面每一项进行比较
使用空容器存储验证是否存过
相邻项方案
sort方法,用于排序,并且是在原来数组基础上进行排序,不生成新数组
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
function bubble(ary){
for(let i = 0;i < ary.length - 1;i++){
//外面一层控制循环几轮
for(let j = 0;j < ary.length-1-i;j++){
//里面一层控制循环到哪个数值
if(ary[j] > ary[j+1]){
[ary[j],ary[j+1]] = [ary[j+1],ary[j]] //ES6解构赋值
}
}
}
}
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
function insert(ary){
let handle = [];
handle.push(ary[0])
for(let i = 1;i<ary.length;i++){
let A = ary[i]
for(let j = handle.length-1;j>=0;j--){
let B = handle[j]
if(A>B){
handle.slice(j,0,A); //将A插在B后面
break;
}
if(j===0){
handle.unshift(A); //如果是比较到第1个还没有大于第1个,那么直接放到数组的最前面
}
}
}
}
unshift方法是在数组头部插入一个元素,并且是在原数组上进行操作不生成新数组
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
function quick(ary){
if(ary.length <= 1){
return ary
}
let middleIndex = Math.floor(ary.length/2) //获取中间项index
let middleValue = ary.splice(middleIndex,1)[0] //将中间项哦从原数组中删除,并且获得删除的中间项的值
let aryLeft = [];
let aryRight = [];
for(let i = 0;i < ary.length;i++){
ary[i]>middleValue?aryRight.push(ary[i]):aryLeft.push(ary[i])
}
//递归方式让左右持续这样处理,一直到左右两边都排好为止。并且左+中+右 就是最后的结果
return quick(aryLeft).concat(middleValue,quick(aryRight))
}
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];
arr = arr.flat(Infinity) //后面传入的参数是扁平化的级数;Infinity表示扁平化无限级
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];
arr = arr.toString().split(',').map((item)=>{
return parseFloat(item)
})
//toString后 "1,2,2,3,4,5,5,6,7,8,9,11,12,12,13,14,10"
//split(',')后 ["1", "2", "2", "3", "4", "5", "5", "6", "7", "8", "9", "11", "12", "12", "13", "14", "10"]
//然后再把每一项转化成数字
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];
arr = JSON.stringify(arr).replace(/\[|\]/g,'').split(', ').map((item)=>{
return parseFloat(item)
})
// JSON.stringify(arr)后 "[[1,2,2],[3,4,5,5],[6,7,8,9,[11,12,[12,13,[14]]]],10]"
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];
arr.join('|').split(/(?:,|\|)/g).map((item)=>{
return parseFloat(item)
})
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];
function flatten(arr){
//some循环数组中的每一项,返回的是true那么就是找到了要求中的。
while(arr.some(item => Array.isArray(item))){
arr = flatten([].concat(...arr)) //[].concat(...arr)这样操作会展开一层。递归继续扁平化处理
}
return arr;
}
some
some验证数组中的某一项是否有符合规则的。some返回的结果不是true就是false
some用于检测数组中是否有符合条件的元素,方法会依次执行数组的每个元素
- 如果有一个满足条件的,则返回true,剩余的元素不会再执行检测
- 没有满足条件的,返回false
every用于检测数组中的元素是否都符合条件
- 如果检测的数组中有一个不满足条件,那么返回false,并且剩余的不再检测
- 如果都满足条件那么返回true
var A = [3, 4, 5, 5] var B = A.some((item)=>{ return item >=5; //只要数组中有大于5的那么B就是true })
find
Find返回的结果如果有就找出这项结果返回,如果没有就返回undefined。并且只查找哦这一项
var A = [3, 4, 5, 5] var B = A.find((item)=>{ return item >=4; //有返回这项值,没有返回undefined })
//循环递归,判断当前项是否是数组,如果不是数组那么存进新数组中,如果是数组那么继续校验
(function(){
function myFlat(){
let result = [],
_this = this;
let fn = (arr) => {
for(let i = 0;i<arr.length;i++){
let item = arr[i]
if(Array.isArray(item)){
//如果是数组那么自己递归继续展开,不是的话直接push到result中
fn(item);
continue;
}
result.push(item)
}
}
fn(_this)
return result;
}
Array.prototype.myFlat = myFlat;
})()
arr = arr.myFlat();
//构造前两项,判断n是否从第三项即值为2开始。是的话,等于前两项之和,采用递归方式
function fabonacci(n){
if(n<=1) return 1; //斐波那契数列前两项,1
let arr = [1,1];
return fabonacci(n-2)+fabonacci(n-1)
}
function fabonacci(n){
if(n<=1) return 1; //斐波那契数列前两项,1
let arr = [1,1];
let i = n +1 - 2; //之后需要构造的项的数量
while(i>0){
arr.push(arr[arr.length-1]+arr[arr.length-2]) //依次构造后一项
i--;
}
return arr[arr.length-1]
}
function fabonacci(count){
fn(count,curr=1,next=1){
if(count=0){
return 1;
}else{
return fn(count-1,next,curr+next) //把当前项作为下一项
}
}
return fn(count);
}
//从N开始,连续M个正数序列的和
function fn(count){
let result = [];
let middle = Math.ceil(count/2)
for(let i = 1;i<=middle;i++){
for(let j = 2;;j++){
//j累加次数
let total = (i+(i+j-1))*j/2 //连续数只之和公式,首项➕尾项 乘以项数 除以2
if(total>count){
break;
}else if(total === count){
result.push(createArr(i,j))
break;
}
}
}
return result;
}
function createArr(n,len){
let arr = new Array(len).fill(null);
arr[0] = n;
arr = arr.map((item,index)=>{
return n+index
})
return arr;
}
call和apply都是function原型上的方法,用于改变this指向的,唯一的区别就是传入参数的形式不一样,call是一个一个传参,而apply把所有参数用数组形式传。bind与他们类似(传参数也是数组形式),都是改变this指向,只是预先处理函数,但是并不会立即执行。 经过测试,call比apply性能要好一些。
//使用apply场景
let arr = [10,20,30],
obj = {
};
function fn(x,y,z){
}
fn.apply(obj,arr) //x,y,z分别为10 20 30
fn.call(obj,...arr) //基于ES6的展开运算符,可以展开依次传递给函数
自己实现性能测试(只供参考,任何代码测试都跟测试环境有关。CPU、内存、GPU等电脑当前性能不会有相同的时间)。console.time
可以测出一段程序执行的时间。在火狐浏览器中可以安装firebug
插件取监控更精确的时间
console.time('A') //A相当于给时间测试起个名字
for(let i = 0;i<100000;i++){
}
console.timeEnd('A')
let reg = /^(?!^[a-zA-Z]+$)(?!^[A-Z0-9]+$)(?!^[0-9a-z]+$)(?![0-9]+$)[a-zA-Z0-9]{
6,16}$/
let ary = $attr('id','AAA')
function $attr(property,value){
let elements = document.getElementByTagName('*'), //获取当前页面的所有标签
arr = [];
//方式一:可以借用数组循环
// [].forEach.call(elements,item=>{
})
//方式二:利用Array.from将类数组非数组转化成为数组
elements = Array.from(elements)
elements.forEach((item)=>{
let itemValue = item.getAttribute(property) //在这个标签中获取property
if(property === 'class'){
//class样式类属性名要做特殊处理,因为class中可能有多个属性值
//判断当前字符串中是否包含着哦个单词
new RegExp(/\b+value+\b/).test(itemValue)?arr.push(item):null
return;
}
if(itemValue === value){
//获取的值和传递的值相等校验成功就是我们需要的
arr.push(item)
}
})
return arr;
}
let str = "test一个测试,just测试一下下smileyqp",
reg = /\b[a-z]+\b/ig; //后面的g是全局匹配,i是忽略大小写
str.replace(reg,value=>{
//value正则捕获的内容
return ' '+value+' '
}).trim(); //trim()去除开头和结尾的空格;trimLeft()去除开头空格;trimRight()去结尾空格
//str "test 一个测试, just 测试一下下 smileyqp"
(5).add(3).minus(2)
使其输出结果为6
(function(){
function check(n){
n = Number(n); //进行检测
return isNaN(n)?0:n //判断是否为有效数字,有效数字返回n,非有效数字返回0
}
function add(n){
n = check(n); //进行有效性检测处理
return this+n; //这里的this是操作的实例,即5,那么this+n即5+3 =》 8
}
function minus(n){
n = check(n);
return this-n;
}
//Number.prototype.add = add;
//Number.prototype.minus = minus;
//也可以如下写,JQ源码走红经常下面这种写法
['add','minus'].forEach((item)=>{
Number.prototype[item] = eval(item); //eval将字符串转化成表达式
})
})()
(5).add(3).minus(2)
function fn(x){
return function(y){
return x+y
}
}
let fn = x => y=>x+y
let obj = {
name:'smileyqp'
}
function fn1(){
console.log(this)
}
fn1.call(obj) //this => obj
let fn2 = ()=>{
console.log(this)
}
fn2.call(obj) //this => window
…arg
传递的参数集合(数组)let fn = (...arg ) =>{
console.log(arg) // [10, 20, 30]
}
fn(10,20,30)
function Fn (){
this.X = 100;
}
fn.prototype.getX = function(){
}
let f = new Fn;
思考题拓展:
题目一:数组上实现一个each方法,实现下面的三个要求
let arr = [10,20,30,'AA'],
obj = {
};
arr = arr.each(function(item,index){
// this => obj 1、第二个参数不传,this指向window
if(!isNaN(item)){
return false; //2、如果不是数字,那么返回的是false
}
return item*10; //3、返回结果是啥就把数组中当前项替换掉
},obj)
//这个方法最后实现的结果是 [100,200,300,false]
题目二:重写replace,replace([REG正则],callback)
let str = 'smileyqp2019smile2020'
str = str.replace(/smile/g,function(...arg){
//arg中存储了每一次大正则匹配的信息和小正则匹配的信息
return ; //返回把正则匹配的替换后的字符串
})
let str = 'smileyqpTestItSMILEYQP@沛沛$3434'
str = str.replace(/a-zA-Z/g,(content)=>{
//每一次正则匹配到的结果
//验证是否为大写:1、转化成大写之后是否和原来一样,一样那原来的为大写,反之之前为小写2、ASCII表中找大写字母的取值范围(65-90)
//1、content.toUpperCase() === content
//2、content.charCodeAt() >=65 || content.charCodeAt <=90
return content.toUpperCase() === content?content.toLowerCase:content.toUpperCase;
})
实现一个字符串匹配算法,从字符串S中查找是否存在字符串T,若存在则返回第一次所在位置,不存在返回-1(不能基于indexOf以及includes等内置方法)
(function(){
function myIndexOf(T){
//this 原始的字符串,即S
let lenT = T.length,
lenS = S.length,
result = -1;
if(lenT>lenTS){
return -1;
}
for(let i = 0;i<lenS-lenT+1;i++){
let substr = S.substr(i,lenT)
if(substr === T){
result = i;
break;
}
}
return result;
}
String.prototype.myIndexOf = myIndexOf;
})()
let S = 'yqp27982348张三smile&&&smile',
T = 'smile'
console.log(S.myIndexOf(T))
(function(){
function myIndexOf(T){
//this 原始的字符串,即S
let reg = new RegExp(T),
res = reg.exect(this);
return res === null ? -1:res .index
}
String.prototype.myIndexOf = myIndexOf;
})()
let S = 'yqp27982348张三smile&&&smile',
T = 'smile'
console.log(S.myIndexOf(T))
1、协议:http https ftp
2、域名 www.smileyqp.com smileyp.cn smile.yqp.smileyqp.com.cn
3、请求路径 index.html /stu. stu/index.html
4、问号传参 ?name=smileyqp&age=18
5、哈希值
协议、请求路径、问号传参、哈希可以省略
let str = 'http://www.smileyqp.com/index.html'
leg reg = /^((http|https|ftp):\/\/)?(([\w-]+\.)+[a-z0-9]+)((\/[^/]*)+)?(?:\?[^# ]+)?(#.+)?$/i;
function Foo(){
Foo.a = function(){
console.log(1)
}
this.a = function(){
console.log(2)
}
}
Foo.prototype.a = function(){
console.log(3)
}
Foo.a = function(){
cosnole.log(4)
}
Foo.a(); //4
let obj = new Foo(); //new Foo()的时候也会吧Foo当作一个函数执行;此时Foo上的属性a =>1,其中this指obj,obj.a =>2
obj.a(); //2
Foo.a(); //1
//html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片懒加载</title>
<style>
*{
margin: 0;
padding: 0;
}
.imgBox{
margin: 1000px auto;
width: 300px;
height: 200px;
overflow: hidden;
background: pink;
}
.imgBox img{
width: 100%;
}
</style>
</head>
<body>
<div class="imgBox">
<img src="" alt="懒加载" data-img="https://gss0.baidu.com/9vo3dSag_xI4khGko9WTAnF6hhy/zhidao/pic/item/3ac79f3df8dcd100bbd10c8e738b4710b8122fcb.jpg"/>
</div>
<script src="node_modules/jquery/dist/jquery.min.js"></script>
<script src="./delayImg.js"></script>
</body>
</html>
//delayImg.js
let $imgBox = $(".imgBox"),
$img = $imgBox.children('img'),
$window = $(window)
/**
* 加载的时机:
* 1、当页面其他的所有资源都加载完成的时候
* 2、当页面滚动到其位置的时候,图片完全出现在视野之中
*/
// $(document).ready();//dom结构加载完成
$(window).on('load scroll',function(){
//在load和scroll两个事件的时候都会触发;JQuery中事件绑定支持多事件绑定,两个事件触发的时候执行相同的事件;
if($img.attr('isLoad')==='true'){
return; //加载过之后不会重新加载
}
console.log('ok')
let $A = $imgBox.outerHeight() + $imgBox.offset().top;
let $B = $window.outerHeight() + $window.scrollTop()
if($A<=$B){
//加载真实图片
$img.attr('src',$img.attr('data-img'))
$img.on('load',()=>{
//加载成功
// $img.css('display','block')
console.log('图片加载成功!')
$img.stop().fadeIn() //fadeIn是jq中的渐现
})
$img.attr('isLoad',true) //attr存储的自定义属性值都是字符串格式'true'
}
});
//html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>多图片懒加载</title>
<style>
*{
margin: 0;
padding: 0;
}
.container{
width: 800px;
margin: 0 auto;
}
.imgBox{
margin: 0px auto;
width: 300px;
height: 200px;
overflow: hidden;
background: pink;
margin-bottom: 20px;
}
.imgBox img{
width: 100%;
}
</style>
</head>
<body>
<div class="container">
<div class="imgBox">
<img src="" alt="懒加载" data-img="https://gss0.baidu.com/9vo3dSag_xI4khGko9WTAnF6hhy/zhidao/pic/item/3ac79f3df8dcd100bbd10c8e738b4710b8122fcb.jpg"/>
</div>
</div>
<script src="node_modules/jquery/dist/jquery.min.js"></script>
<script src="./moredelayImg.js"></script>
</body>
</html>
//moredelayImg.js
let $container = $('.container'),
str = ``,
$imgBoxs = null,
$window = $(window)
new Array(20).fill().forEach((item)=>{
//new Array(20).fill() 创造长度为20的数组每一项用null填充
str+=''
})
console.log(str)
$container.html(str);
$imgBoxs = $container.children('.imgBox');
//多张图片延迟加载
$window.on('load scroll',()=>{
//获取浏览器距离body的距离
let $B = $window.outerHeight() + $window.scrollTop()
console.log($imgBoxs)
//循环获取每一张图片区域,根据自己距离body的距离计算出里面的图片是否进行加载
$imgBoxs.each((index,item)=>{
console.log(index,item)
let $item = $(item),
$itemA = $item.outerHeight() + $item.offset().top,
isLoad = $item.attr('isLoad')
if($itemA <= $B && isLoad !== 'true'){
//如果这个盒子已经懒加载过依次那么就不再次进行懒加载处理
$item.attr('isLoad',true);
$img = $item.children('img')
$img.attr('src',$img.attr('data-img'))
$img.on('load',()=>{
//加载成功
// $img.css('display','block')
console.log('图片加载成功!')
$img.stop().fadeIn() //fadeIn是jq中的渐现
})
}
});
})
let nums1 = [1,2,3,2]
let nums2 = [2,2,2]
let arr = [];
for(let i = 0;i<nums1.length;i++){
let item1 = nums1[i]
for(let j=0;j<nums2.length;j++){
let item2 = nums2[j]
if(item1===item2){
nums1[i] = null;
nums2[j] = null;
arr.push(item1)
break;
}
}
}
console.log(arr) //[2,2]
let nums1 = [1,2,3,2]
let nums2 = [2,2,2]
let arr = [];
nums1.forEach((item,index)=>{
let n = nums.indexOf();
if(n>0){
nums1.splice(index,1)
nums2.splice(n,1)
arr.push(item)
}
})
给定一个数组,将数组中的元素向右移k个位置,其中k是非负数,例如:
输入:[1,2,3,4,5,6]和k=3
输出:[5,6,1,2,3,4]
输入:[1,-100,78,90] k = 2
输出:[90,1,-100,78]
function rotate(key){
//参数处理,key>0
if(key<0||key === 0||key === this.length)return this;
if(key>this.length){
key = key%this.length}
//slice支持负数索引,直接就是数组的后几位
return this.slice(-key).concat(this.slice(0,this.length-key))
}
Array.prototype.rotate = rotate;
let arr = [1,2,3,4,5,6,7],
k = 3;
arr.rotate(3); // [5, 6, 7, 1, 2, 3, 4]
slice
参数:开始点,结束点,返回:切割的数组
splice
参数:开始点、长度,返回:删除的这部分返回
输入:[1,-100,78,90] k = 2
输出:[90,1,-100,78]
function rotate(key){
//参数处理,key>0
if(key<0||key === 0||key === this.length)return this;
if(key>this.length){
key = key%this.length}
return this.splice(this.length-key,key).concat(this)
}
Array.prototype.rotate = rotate;
let arr = [1,2,3,4,5,6,7],
k = 3;
arr.rotate(3); // [5, 6, 7, 1, 2, 3, 4]
输入:[1,-100,78,90] k = 2
输出:[90,1,-100,78]
//写法一:
function rotate(key){
//参数处理,key>0
if(key<0||key === 0||key === this.length)return this;
if(key>this.length){
key = key%this.length}
for(let i = 0;i<=key;i++){
this.unshift(this.pop()); //this.pop()最后一项;unshift首部插入
}
return this;
}
Array.prototype.rotate = rotate;
//写法二:
function rotate(key){
//参数处理,key>0
if(key<0||key === 0||key === this.length)return this;
if(key>this.length){
key = key%this.length}
new Array(k).fill('').forEach((item)=>{
this.unshift(this.pop()); //this.pop()最后一项;unshift首部插入
})
return this;
}
Array.prototype.rotate = rotate;
let arr = [1,2,3,4,5,6,7],
k = 3;
arr.rotate(3); // [5, 6, 7, 1, 2, 3, 4]
let obj = {
name:'OBJ'
}
function fn(...arg){
console.log(this,arg)
}
document.body.onclick = fn; //this=>BODY
document.body.onclick = function(ev){
//=>ev 事件对象:给元素的某个事件绑定方法,当事件触发会绑定这个方法,并且把当前事件的相关信息传递给这个函数事件对象
}
实现:
- 点击时候,this指向修改成obj,并传入事件对象以及两个参数100,200
//bind就是一个最经典的柯里化
(function(){
//context就是传入的obj用来改变this指向的,如果没有就默认写的是window
function myBind(context=window,...outerArgs){
let _this = this;
return function(...innerArgs){
_this.call(context,...innerArgs.concat(outerArgs))
}
}
Function.prototype.myBind = myBind;
})()
let obj = {
name:'OBJ'
}
document.body.onclick = fn.myBind(obj,100,200)
函数的科里化:是利用闭包的保存思想,也就是函数执行形成一个闭包,存储一些变量值,当要使用的时候再使用
闭包的两大作用:
最简单科里化函数编程思想示例。科里化=》闭包。形成闭包,里面的参数供子集使用。
function fn(x){
//相当于预先在闭包中把值存储起来
return function(y){
return x+y
}
}
fn(100)(200)
//第一次执行fn(100),执行完成之后当前作用域销毁,但是形成闭包值保留,进行第二个方法执行
请实现一个add函数实现以下功能
add(1) //1
add(1)(2) //3
add(1)(2)(3) //6
add(1)(2)(3)(4) //10
add(1)(2,3) //6
add(1,2)(3) //6
add(1,2,3) //6
function currying(fn,length){
//函数的length是获取它有多少个形参
length = length || fn.length;
return function(...args){
if(args.length >= length){
return fn(...args)
}
return curring(fn.bind(null,...args),length-args.length)
}
}
let add = currying((...args)=>{
return eval(args.join('+')) //求args里面值相加的和
},5) //这个后面的5是总共要求几个字数的和,比如这里求五个的。这里是不管几次调用函数,只是参数的数量
function Dog(name){
this.name = name;
}
Dog.prototype.bark = function(){
console.log('bark')
}
Dog.prototype.sayName = function(){
console.log('my name is'+this.name)
}
要实现一个_new方法实现以下:
let dog = _new(Dog,'dooooog')
dog.sayName()
dog.bark()
1、像普通函数执行一样、形成一个私有的作用域
2、默认创建一个对象,然函数中的this指向这个对象,这个对象就是当前实例
3、代码执行
4、默认把创建对象返回
function _new(Fn,...arg){
//Fn当前要new的类;arg是给构造函数传的参数
//let obj = {
};
//Fn.call(obj,...arg)
let obj = Object.create(Fn.prototype); //创建原型链为Fn.prototype的对象实例
obj.__proto__ = Fn.prototype;
return obj;
}
注意:
Object.create
是创建一个空对象,并且对象的原型链指向我们传入的那个参数(即:让我们创建的这个空对象作为我们传入的那个参数的实例)
let ary1 = ['A1','A2','B1','B2','C1','C2','D1','D2']
let ary2 = ['A','B','C','D']
//因为使用arr.sort((a,b)=>a.localeCompare(b)),时候没有Z,ABCD这些会在最前面
ary2 = ary2.map(item=>item+'Z')
let arr = ary1.concat(ary2)
arr.sort((a,b)=>a.localeCompare(b)).map(item=>{
return item.replace('Z','') //去除Z
})
console.log(arr) //["A1", "A2", "A", "B1", "B2", "B", "C1", "C2", "C", "D1", "D2", "D"]
let ary1 = ['D1','D2','A1','A2','C1','C2','B1','B2']
let ary2 = ['B','A','D','C']
//要求合并输出的数组为["D1", "D2", "D","A1", "A2", "A","C1", "C2", "C","B1", "B2", "B"]
let n = 0;
for(let i = 0;i<ary2.length;i++){
let item2 = ary2[i]
for(let j = 0;j<ary1.length;j++){
let item1 = ary1[j]
if(item1.includes(item2)){
n = j; //包含记录索引,后面有包含会更新这个值。保存最后一项匹配的索引
}
}
//把当前的值插入到索引的后面
ary1.splice(n+1,0,item2) //从n+1删除0项,将item2插入n+1的前面,也就相当于n后面插入n+1
}
console.log(ary1) //["D1", "D2", "D", "A1", "A2", "A", "C1", "C2", "C", "B1", "B2", "B"]
for(var i = 0;i<10;i++){
setTimeut(()=>{
console.log(i)
},10000)
}
//输出10次10,因为不是私有变量。setTimeout是异步的
//将var改成let,let定义的局部块的变量,每次循环都会在当前块作用域中形成私有变量i,定时器执行的时候所使用的i是所属作用域的i
for(let i = 0;i<10;i++){
setTimeut(()=>{
console.log(i)
},10000)
}
//输出0,1,2,3,...,因为不是私有变量。setTimeout是异步的
//使用闭包保存i
for(var i = 0;i<10;i++){
(function(i){
setTimeut(()=>{
console.log(i)
},10000)
})(i)
}
//使用闭包也可以这样写
for(var i = 0;i<10;i++){
setTimeut(((i)=>{
return ()=>{
console.log(i)
}
})(i),10000)
}
//也可以使用bind保存i的值,基于bind预先处理机制。循环的时候就将bind要预先处理的函数传过去
var fn = function(i){
console.log(i)
}
for(var i = 0;i<10;i++){
setTimeut(fn.bind(null,i),10000)
}
let fn = unction AAA(){
console.log(AAA) //当前函数
}
var b = 10;
(function b(){
b = 20
console.log(b) //function b;b此时相当于一个常量,不能被改变
})()
console.log(10) //10
var b = 10;
(function(){
//去掉匿名函数的名称b之后,里面的b就变成全局的了
b = 20
console.log(b) //20
})()
console.log(10) //20
现在要让上面的匿名函数中的b的值log变成20,并且全局b仍然是10,怎样实现?
//方法一:改为形参
var b = 10;
(function b(b){
//形式参数
b = 20
console.log(b) //20
})()
console.log(10) //10
//方法二:声明它
var b = 10;
(function b(){
let b = 20 //或者使用var声明也可以
console.log(b) //20
})()
console.log(10) //10
var a = ?
使得a==1&&a==2&&a==3
#####==
&===
==
进行比较,如果左右两边的数据类型不相同的话那么先转换成相同的数据类型,然后再进行比较
{}=={}
两个对象进行比较时比较堆内存的地址null==undefined
相等的,但是三个等号就不相等NaN == NaN
不相等,NaN和谁都不相等[12]=="12"
对象和字符串项比较,是把对象toString转换成字符串之后进行比较null
转化成数字0undefined
转化成数字NaN[12]==true 都是转化成数字. => Number([12].toString()) 输出12 == 1 不相等
[] == false []=> 0 false => 0 相等
[] == 1 []=>0 不相等
"1" == 1 相等
true == 2 不相等
var a = ?
使得a==1&&a==2&&a==3
给对象添加一个私有的toString方法,重构私有方法
使得a==1&&a==2&&a==3成立
对象要先toString然后进行转化成数字
var a = {
n:0,
toString:function(){
//所有的值调用toString都是先看自己私有有没有,没有再原型链上找
return ++ this.n;
}
}
//shift删除数组第一项,返回删除的内容,原有的数组改变
var a = [1,2,3]
a.toString= a.shift
a==1&&a==2&&a==3
let n = 0;
Object.defineProperty(window,'a',{
get:function(){
return ++n;
}
})
//设置成全局变量实际并不好,可以优化
Object.defineProperty(window,'a',{
get:function(){
this.val?this.val++:this.val=1;
}
})
ES6新增方法
Array
String.fromCharCode(122)
=> z. 'z'.charCodeAt()
=> 122
Object.create([obj])
创建空对象,原型链指向空对象
Object.defineProperty
用于定义某个对象中的参数,三个参数:对象、属性、值
let obj = {
name:'Jane'
}
//Object.defineProperty(obj,'name','smileyqp') //每次获取时候会触发get方法,于是可以从get方法着手
//监听获取和设置
Object.defineProperty(obj,'name',{
get:function(){
return 'smileyqp'
},
set:function(){
return 'Mary'
}
})
let obj = {
2:3,
3:4,
length:2,
push:Array.prototype.push
}
obj.push(1) //=> obj[obj.length] = 1 => obj[2]=1 length原来基础上加一 obj[length] => 3
obj.push(2) =》obj[obj.length] = 2 => obj[3]=2 length原来基础上加一 obj[length] => 4
console.log(obj)
//所以obj值如下
let obj = {
2:1,
3:2,
length:4,
push:Array.prototype.push
}
//数组push方法原理
Array.prototype.push = function(val){
this[this.length] = val;
return this.length; //现在这个长度是数组增加1之后了的长度
}
//push往数组末尾新增一个元素,返回的是数组的长度
let obj = {
1:2323,
4:3492,
8:2673
}
要求得到[2323, null, null, 3492, null, null, null, 2673, null, null, null, null]
let obj = {
1:2323,
4:3492,
8:2673
}
let arr = new Array(12).fill(null).map((item,index)=>{
return obj[index+1]?obj[index+1]:item
})
let obj = {
1:2323,
4:3492,
8:2673
}
obj.length = 13 //因为要截取后面的12个
//Array.from(obj) 后的值[undefined, 2323, undefined, undefined, 3492, undefined, undefined, undefined, 2673, undefined, undefined, undefined,undefined]
Array.from(obj).slice(1).map(item=>{
//slice是动某个索引值开始
return item?item:null
})
let obj = {
1:2323,
4:3492,
8:2673
}
//Object.keys,是遍历对象中的所有key并且以数组的方式返回
let arr = new Array(12).fill(null)
Object.keys(obj).forEach(item=>{
arr[parseInt(item)-1] = obj[item]
})
深拷贝
let a = 100;
let b = 1;
a = 200;
console.log(b) //100
let a = {
age:10};
let b = a;
b.age = 20;
console.log(a.age) //20
常见值类型
常见引用类型
obj
array
null 特殊引用类型,指向的地址为空地址
function 特殊引用类型,不用于存储数据,所以没有’拷贝、赋值函数’这一说法
const obj1 = {
x:100,y:200}
const obj2 =obj1;
let x1 = obj1.x; //干扰作用,值类型直接赋值过去,之后再没有关系
obj2.x = 101;
x1 = 102;
console.log(obj1) //{
x:101,y:200}
function deepClone(obj = {
}){
if(typeof obj !== 'object'||obj === null){
return obj;
}
//初始化返回结果
let result;
if(obj.instanceof Array){
result = []
}else{
result = {
}
}
for(let key in obj){
if(obj.hasOwnProperty(key)){
//保证是这个对象自己拥有的私有属性,不是原型的属性
//调用递归
result[key] = deepClone(obj[key])
}
}
return result;
}
const a = 100+10; //110
const b = 100+'10'; //10010
const c = true+'10'; //true10
100 == '100'; //true
0 == ''; //true
0 == false; //true
false ==''; //true
null == undefined; //true
除了
==null
之外其他的一律用===
。并且,例如:a==null
相当于a===undefined||===null
!!a===true
!!a===false
class Student{
constructor(name,age){
this.name = name;
this.age = age;
}
sayHi(){
console.log('hi'+this.name+this.age)
}
}
let stu = new Student('smileyqp',100)
class Person(){
constructor(name,age){
this.name = name;
this.age = age;
}
eat(){
console.log(this.name+'eat food')
}
}
class Student extends Person{
constructor(name,age,number){
super(name,age)
this.number = number;
}
sayHi(){
//扩展方法
console.log('hi'+this.name+this.age)
}
}
class Teacher extends Person{
constructor(name,age,major){
super(name,age)
this.major = major;
}
teach(){
console.log(this.name+'teach'+this.major)
}
}
let smileyqp = new Student('smileyqp',20)
//补充
smileyqp.eat() //smileyqp eat food
smileyqp.__proto__.eat() //会爆粗,因为它相当于在smileyqp.__proto__即Student的原型上去调用的,没有定义name所以会报错
smileyqp instanceof Student; //true
smileyqp instanceof Person; //true
smileyqp instanceof Object; //true
[] instanceof Array; //true
{
} instanceof Object; //true
[] instanceof Object; //true
prototype
__proto__
__proto__
都指向对应的class的原型prototype
//隐式原型和显式原型(案例demo接上个题目的案例)
console.log(smileyqp.__proto__) //隐式原型
console.log(Student.prototype) //显式原型
console.log(smileyqp.__proto__ === Student.prototype) //true
__proto__
中查找console.log(Student.prototype.__proto__)
console.log(Person.prototype)
console.log(Student.prototype.__proto__ === Person.prototype)
//jquery做dom查询的
class jquery{
constructor(selector){
cons result = documnent.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)
})
}
}
const $p = new jQuery('p')
$p.get(1)
$p.each(elem=>console.log(elem.nodeName))
$p.on('click',()=>{
alert('click')})
//1、插件机制
jQuery.prototype.dialog = function(info){
alert(info)
}
//2、造轮子
class myJQuery extends jQuery{
constructor(selector){
super(selector)
}
//扩展自己的方法
addClass(className){
}
style(data){
}
}
//创建10个<a/>标签,点击的时候弹出来对应的序号
let i,a;
for(i = 0;i<=10;i++){
a = document.createElement('a');
a.innerHTML = i+"
";
a.addEventListener('click',function(e){
e.preventDefault();
alert(i) //10
})
document.body.appendChild(a)
}
let a;
for(let i = 0;i<=10;i++){
//let i是定义块作用域
a = document.createElement('a');
a.innerHTML = i+"
";
a.addEventListener('click',function(e){
e.preventDefault();
alert(i) //1,2,3,4,5...
})
document.body.appendChild(a)
}
作用域:变量的合法的使用范围
作用域分类
//ES6新增
if(true){
let x = 100; //这里用乐天声明的变量或者const声明的常量,他们的作用域都是这个块之内
}
console.log(x); //会报错
xx is undefined
//函数作为返回值,返回之后再执行
function create(){
let a = 100;
return function(){
console.log(a)
}
}
let fn = create();
let a = 200;
fn(); //100 函数执行是在全局作用域;函数的定义在create函数里面,往上级作用域寻找
//函数作为参数,通过参数传入之后再执行
function print(fn){
let a = 200;
fn()
}
let a = 100;
function fn(){
console.log(a)
}
print(fn); //100
function fn1(){
console.log(this)
}
fn1(); //window
fn1.call({
x:100}) //{
x:100}
const fn2 = fn1.bind({
x:200})
fn2(); //{
x:200}
箭头函数没有this,其中this取上级作用域this
//demo1
const zhansan = {
name:'zhangsan',
sayHi(){
console.log(this) //this当前对象
},
wait(){
setTimeout(function(){
console.log(this) //this window
})
}
}
//demo1
const zhangsan = {
name:'zhangsan',
sayHi(){
console.log(this) //当前对象
},
waitAgain(){
setTimeout(()=>{
console.log(this) //当前对象
})
}
}
function.prototype.mybind = function(){
//将参数拆解为数组
const args = Array.prototype.slice.call(arguments);
//获取this(数组第一项)
const t = args.shift()
//fn1 bind中的fn1
const self = this;
return function(){
return self.apply(self,args)
}
}
function createCache(){
const data = {
} //闭包中的数据,隐藏不被外界访问
return {
set:function(key,val){
data[key] = val;
},
get:(){
return data[key]
}
}
}
//隐藏不被外界访问,是指c只能通过set,get设置获取。只提供api访问,其他方式改不了
const c = createCache();
c.set('a',100);
c.get('a');
//异步
console.log(1)
setTimeout(()=>{
console.log(2)
},1000)
console.log(3)
//同步
console.log(1)
alert(2)
console.log(3)
const src = '../xxximg.png'
function loadImg(){
return new Promise((resolve,reject)=>{
let img = document.createElement('img')
img.onload = function(){
console.log('loaded')
resolve(img)
}
mg.onrror = function(){
reject(new Error(`图片加载失败${
src}`))
}
img.src = src
})
}
loadImg().then(img=>{
console.log(img)
return img
}).catch(err){
console.log(err)
}
前端使用的异步的场景有哪些
网络请求,比如:ajax图片加载
onsole.log('start')
let img = document.createElement('img')
img.onload = function(){
onsole.log('loaded')
}
img.src = './xxx.png'
console.log('end')
定时任务,比如:setTimeout
单线程和异步(异步是由单线程这个背景来的)
JS是单线程语言,只能同时做一件事
浏览器和nodejs已支持Js启动进程如web worker。但是并不能改变js是单线程的本质
Js和dom渲染共用同一个线程,因为js可以修改dom结构
遇到等待(网络请求、定时任务)不能卡住
需要异步,解决单线程的问题
回调callback函数形式
typeof能判断哪些类型
何时使用=何时使用
值类型和引用类型的区别
手写深拷贝
this的不同引用场景,如何取值
手写bind函数
实际开发中遇到的闭包场景,并且举例说明(eg:隐藏数据,只提供api)
//创建10个a标签,点击依次弹出1,2,3,4,5,6,7,8,9,10
let a;
for(let i = 0;i<=10;i++){
a = document.createElement('a')
a.innerHTML = i+'';
a.addEventListener('click',function(e){
e.preventDefault()
alert(i)
})
document.body.appendChild(a)
}
前言
DOM属于哪种数据结构
DOM操作的常用API
attr(attribute)和property的区别
一次性插入多个dom节点,考虑性能
html实际上也是一种特殊的xml
DOM节点的操作
const pList = ducument.querySelectorAll('p')
const p = pList[0]
p.getAttribute('data-name')
p.setAttribute('data-name','smileyqp')
p.getAttribute('style')
p.setAttribute('style','font-size:30px')
const pList = ducument.querySelectorAll('p')
const p = pList[0]
console.log(p.style)
console.log(p.style.width)
console.log(p.style.className)
console.log(p.nodeName) //nodeName节点的名称
console.log(p.nodeType) //nodeType节点的类型
DOM结构的操作
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')
//新建节点
const newP = document.createElement('newP')
newP.innerHTML = 'this is newP'
//插入节点
div1.appendChild(newP)
//移动节点
div2.appendChild(p1) //将一个已经存在于dom中的元素append到另外一个中去,那么就是将节点移动过去的
//获取父元素
console.log(p1.parentNode)
//获取子元素列表;类似乎组转化成数组Array.prototype.slice.call,然后过滤类型为1,即p的元素节点
div1.childNodes();
const div1childNodesP = Array.prototype.slice.call(div1.childNodes()).filter((child)=>{
if(child.nodeType === 1){
return true;
}
})
//删除子节点
div1.removeChild(div1childNodesP[0])
DOM性能
//不缓存dom查询结果
for(let i = 0;i<document.getElementByTagName('p').length;i++){
//每次查询都会重新去计算length,频繁进行dom查询
}
//缓存dom查询的结果
const plist = document.getElementByTagName('p')
const plength = plist.length;
for(let i = 0;i<plength;i++){
//缓存dom查询,只需要进行一次dom查询
}
const listNode = document.getElementById('list')
//创建一个文档片段,此时还没有插入dom
const frag = document.createDocumentFragent();
//执行插入
for(let x = 0;<=10;x++){
const li = document.createElement('li');
i.innerHTML = 'list item'+x;
frag.appendChild(li)
}
//都完成之后再插入dom树中
listNode.appendChild(frag)
//navigator
const ua = navigator.userAgent; //获取当前浏览器的信息
const isChrome = ua.indexOf('Chrome')
console.log(isChrome)
//screen
onsole.log(screen.width)
cobsole.log(screen.height)
//location
console.log(location.href)
console.log(location.protocol)
console.log(location.pathname)
console.log(location.search) //获取url传的参数
console.log(location.hash) //获取哈希,也就是#后面的内容
//history
history.back();
history.forward()
e.target
来获取触发元素const btn = document.getElementById('btn1')
btn.addEventListener('click',event=>{
console.log('click')
})
//普通绑定:简易通用的事件绑定函数;详细通用事件绑定函数在下面将会提到
function bindEvent(elem,type,fn){
elem.addEventListener(type,fn)
}
const a = document.getElementById('link1')
bindEvent(a,'click',e=>{
e.preventDefault(); //阻止默认行为;比如组织链接的点击跳转
console.log(e.target); //获取点击的元素
alert('this is aaa')
})
stopPropagation
可以阻止冒泡//代理绑定
function bindEvent(elem,type,selector,fn){
//selector要筛选的触发元素选择器
if(fn == null){
//说明传的是三个参数;也就是知识普通绑定,第三个参数就是fn
fn = selector;
selector = null;
}
elem.addEventListener(type,event=>{
//event.preventDefault();
const target = event.target;
if(selector){
//有selector也就是代理绑定(存在冒泡情况)
if(target.matches(selector)){
//判断当前元素是否符合我们传入的这个选择器
fn.call(target,event)
}
}else{
//没有selector,普通绑定情况
fn.call(target,event) //因为需要fn的this指向这个元素,所以要call绑定一下当前触发的元素
}
})
}
//代理绑定
const div3 = document.getElementById('div3')
bindEvent(div3,'click','a',function(event){
//注意:这里不能用箭头函数,因为里面this指向
event.preventDefault();
alert(this.innerHTML)
})
function ajax(url){
const p = new Promise((resolve,reject)=>{
const xhr = new XMLHttpRequest();
xhr.open('GET','data/test.json',true); //true的意思是异步请求
xhr.onreadystatechange = function(){
if(xhr.readystate === 4){
if(xhr.status === 200){
resolve(JSON.parse(xhr.responseText))
}else if(xhr.status === 404){
reject(new Error('404 not found!'))
}
}
}
});
xhr.send(null)
return p;
}
const url = '/data/test.json'
ajax(url).then(res=>{
console.log(res)
}).catch(err=>{
console.log(err)
})
//手写简易的ajax
//get请求;post请求差不多
const xhr = new XMLHttpRequest();
xhr.open('GET','data/test.json',true); //true的意思是异步请求
xhr.onreadystatechange = function(){
if(xhr.readystate === 4){
if(xhr.status === 200){
alert(xhr.responseText)
}else if(xhr.status === 404){
console.log('404 not found ')
}
}
}
xhr.send(null)
ajax请求时,浏览器要求当前网页和server必须同源(安全)
同源:协议、域名、端口三者必须一致
加载图片、css、js可以无视同源策略
(注意:有的图片可能做了防盗链)
可以做统计打点,可使用第三方统计服务
和
可以使用CDN,CDN一般都是外域
可以实现JSONP
可以绕过跨域限制
可以获得跨域数据,只要服务端愿意返回document.cookie=...
来修改document.cookie=…
来修改,太过简陋window.onload
和DOMContentLoad
的区别
document.addEventListener('load',()=>{
console.log('window loaded')
})
document.addEventListener('DOMContentLoaded',()=>{
console.log('dom loaded')
})
则暂停渲染,优先加载并执行js代码完成再继续(js进程和渲染进程共一个线程。script中可能有代码改了之前执行的结果,所以遇到script先暂停渲染)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RGPsZyBB-1621331614316)(/Users/yqp/Library/Application Support/typora-user-images/image-20210518173205903.png)]
SSR(server side render)
懒加载