学习笔记基本类型与引用类型
在这里想说一下基本类型与引用类型的两大点不同,1、可以进行的操作不同。2、复制的方式不同。
我们可以为一个引用类型值例如对象,添加属性和方法,但是对于基本类型值就不能这么操作了。
var obj = new Objec();
obj.name = 'hyt';
console.log(obj.name);//hyt;
var str = 'hyt';
str.age = 18;
console.log(str.age);//undefined;
//只能给引用类型值动态地添加属性及方法;
另外关于复制的方式也是不一样的。基本类型值保存在栈中,引用类型值保存在堆中。复制引用类型值时,它的副本是一个指针,指向堆中的一个对象,复制操作完后,两个变量将指向堆中的同一个对象。因此,改变一个变量,将影响另一个变量。
例如:
//引用类型
var obj = new Object();
obj1 = obj;
obj1.name = 'hyt';
var obj2 = obj1;
obj2.name = 'wl';
console.log(obj1.name); // wl;
//基本类型
var str = 5;
var str1 = str;
str1 = 10;
console.log(str);//5
以上就是基本类型值与引用类型值的不同,那么怎样复制引用类型值,可以不改变它之前的值呢?这里简单说一下深拷贝及浅拷贝。
浅谈深拷贝及浅拷贝
所谓浅拷贝就是复制一下引用类型值,旧变量会随新变量一起改变。而当我们的目标是改变引用类型值,旧变量不随新变量改变,那就需要进行深拷贝。
关于深拷贝还细分只拷贝第一层级,我们深拷贝第一层级一个数组和对象来做个栗子。
数组的深拷贝第一层级方法总结:
- slice() :从已有的数组中截取一部分元素片段组成的新数组(不改变原来的数组!)
用法:array.slice(start,end);start表示是起始元素的下标,end表示的是终止元素的下标。当slice()不带任何参数的时候,默认返回一个长度和原数组相同的新数组
- concat():用于连接两个或多个数组。( 该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。)
用法:array.concat(array1,array2,......,arrayN)因为我们上面调用concat的时候没有带上参数,所以var copyArray = array.concat();实际上相当于var copyArray = array.concat([]);也即把返回数组和一个空数组合并后返回
- 直接遍历;循环遍历数组中的值,把它复制给新的数组;
var arr = [1,2,3,4,5];
function copy(array) {
var newArr = [];
for (let item of array){
newArr.push(item);
};
return newArr;
}
var newArrVal = copy(arr);
newArrVal[0] = 'hyt';
console.log(arr);
console.log(newArr);//错误,你要打印的是函数返回来的值,应该把函数返回的值赋给一个变量,而不是直接打印newArr;newArr依然是一个空数组。
console.log(newArrVal);
MY PROMBLEM:
function c() {
var newArr = [1,2,3];
}
console.log(newArr);//为啥打印出来是[];不是undefined?因为newArr是局部变量所以赋值不会在函数外部得到,但是被声明了所以不会是undefined。
MY PROMBLEM:
使用箭头函数完成上述功能;
MY PROMBLEM:
es6的generator的用处,可以做一些面向对象做的事情,可以更方便的写一个斐波那契数,可以将多层嵌套的ajax变得更为优雅,可以解决闭包也能解决的循环返回最后值的Bug,它就是可以返回多个函数的生成器。
深拷贝一级对象
对象的深拷贝第一层级方法总结:
- es6中的Object.assign。
- 循环遍历对象拷贝。
var obj = { name: 'hyt' , age: 18};
function copy(copyObj) {
let obj1 = {};
for(let item in copyObj) {
obj1[item] = copyObj[item];
};
return obj1;
};
var r = copy(obj);
r.name = 'wl';
console.log(obj);
console.log(r);
以上方法都只适用于数组or对象的一级深拷贝,当数组的元素不再是字符串,或者是对象,数组,当对象是更为复杂的json数据结构,以上方法将无法深拷贝对象。
1、终极大招:递归遍历对象所有属性、属性下的子属性,直至基本类型值进行复制。
var obj1 = {
person : {
name : 'hyt',
age : 18
}
};
function copy (cObj) {
let obj;
//if (typeof cObj !==Object) {
//return;
//}
//cObj intanceof Object ? obj = {} : obj = [];错误写法..
//也可以写作:
let obj = cObj.constructor === Object ? {} : [];
for (let i in cObj) {
//obj[i] = typeof cObj[i] !== Object ? obj[i] = cObj[i] : copy (cObj[i]) ;
if(typeof cObj[i] !== Object) {
obj[i] = cObj[i];
} else {
copy (cObj[i]) ;
}
}
return obj;
}
var newO = copy(obj1);
console.log(a);
2、ES6语法扩展运算符...
let obj = {name:'hyt'};
let newObj = [...obj];
//也可以实现深拷贝;
3、JSON.parse() JSON.stringify();
函数中的参数按值传递
在此只说当一个对象作为参数传递到函数中,也是按值传递的,虽然在函数外部可以看到对象的值变了,但是再函数内部再给同名参数new一个新对象,它是不会被改变的。为什么对象是按引用访问的,因为js规定我们不可以直接操作一个对象,只是操作的它的引用。
JS中的高阶函数
即函数作为参数传入到函数中,例如reduce();map();sort()方法的使用,其参数需要一个计算函数。
JS原型链
原型链是一个较为难理解的概念,对比java这样的动态语言来说,它实现继承是通过类的概念,通过接口等方式可以实现继承,而js这样的静态语言没有类的概念(虽然在es6、es7中已经新加了类),它实现继承的方式就是原型链。
为了实现数据共享,减少资源的消耗,所以需要继承的功能。
什么是原型链呢?它又是如何实现继承的呢?首先抛出两个概念,prototype和_proto_。
prototype是函数才有的显示原型链,_proto_是所有对象都有的隐式原型链。(在js中,一切数据类型都是对象,函数也是对象
对象的_proto_指向它的构造函数的prototype,当查找一个对象的属性时,首先查找对象本身有没有该属性,若没有,将向它的构造函数上查找其prototype下是否有该属性,依此规则向上查找,直至找到构造函数的prototype==null时。
var a = function(){};
console.log(a.__proto__ === Function.prototype);// true; a的构造函数是Function;
console.log(a.__proto__ === Object.prototype);// fales; Function的构造函数是Object;不可跨级访问构造函数的protptype;
把构造函数中不变的属性,挂在prototype上,这样即使构造函数生成了10个实例,实例的prototype上挂的那个属性就是相等的,即它们都指向同一个指针。这样就提高了效率。
例:
function Dog(name) {
this.name = name;
this.species = '犬类';//实例中不变的部分可以写在Dog的prototype上,以提高效率。
};
var dogA = new Dog(), dogB = new Dog();
var test1 = dogA.species == dogB.species;//false;A和B是两个不同的实例,属性也指向各自的指针。
Dog.prototype.species = '犬类';
var test2 = dogA.species == dogB.species; //true;
console.log( test1);
console.log( test2 );
学习笔记instanceof
关于检测数据类型的方式之一instanceof
我们知道typeof和instanceof都可以检测一个变量是什么类型的,但是typeof操作符对于基础数据的检测较为方便,不能检测一个对象具体是什么类型。
instansof是用来具体检测引用类型是什么类型的。
语法:
变量 instanceof constructor(构造函数)
var obj = {}; //obj = new Object(); 左侧的写法是右侧的语法糖而已
obj instanceof Object; // true; obj是Object()的实例。
var arr = [];//同1
arr instanceof Array; // true;
var fn = function(){};
fn instanceof Function; // true;
fn instanceof Object();
var date = new Date();
date instanceof Object;//true;
数据类型分引用类型+基本类型,基本类型包含:string、boolean、number、undefind、null,引用类型包含array、object、date等。
所有引用类型值都是Object构造函数的实例,所以检测所有引用类型值和Object构造函数的关系时,都会返回true,检测基本数据都会返回false,因为基本数据都不是对象。
浅谈JS单线程到同步异步
我们都知道javascript是浏览器端的语言,用来操作dom,和用户做一些交互,想象一下如果js是多线程的,那我们同时选择增加一个dom,删除一个dom,那这个时候该执行哪个操作呢?正是因为不想让js过于复杂,所以js是单线程的。(但是为了利用多核cpu的计算能力,h5提出了web woker标准,允许js创建多个线程,但是子线程受主线程控制,且不可操作dom,所以依然不影响js是单线程的本质。)
JavaScript中的线程包括函数调用、I/O设备(如向服务器发送请求获取响应等)、定时器、用户操作的事件(click、keyup、scroll等)。
既然js是单线程的,那就意味着执行线程时需要排队,依次执行任务,但是当我们遇到例如ajax请求数据这样的线程,由于I/O设备很慢,而cpu又是空闲的,这样无形中浪费了cpu的计算能力,于是js意识到,可以把类似I/O设备的线程先放一边,将需要等待的任务暂时挂起,先执行其他主线程的任务,等完事后再执行挂起的任务。
所以就有了同步和异步两种任务。同步任务即为在主线程中排队等待执行的任务,异步任务是被推入队列(task queue)中,等待执行的任务。
异步任务执行规则:
(1)所有同步任务都在主线程上执行,形成一个执行栈
(2)主线程之外,还存在一个”任务队列”。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
虽然js是单线程的,但是浏览器是多线程的,轮询event loop的就是浏览器,它有专门做这件事的线程。
EventLoop
事件环是操作系统的一种运行机制,js中存在3个概念:
- 栈
- 队列
- 微队列(micro queue)
认识浏览器中的任务源(task): 微任务:then 、messageChannel 、mutationObersve; 宏任务:setTimeout、setInterval、setTmmediate(只兼容ie)
微队列比队列的优先级高,promise()就是微队列中的操作,而ajax,setTimeout等都是队列中的操作。
例题:
function execution() {
setTimeout(()=>{
console.log(1);
},0);
new Promise(function e(resolve){
console.log(2);
for(let i = 0; i++; i < 9999) {
i==9999 && resolve();
}
console.log(3);
}).then(function(){
console.log(4);
});
console.log(5);
//打印顺序:235(4)1
}
//eg2
console.log(1);
console.log(2);
setTimeout(function () {
console.log('setTimeout1');
Promise.resolve().then(function () {
console.log('promise');
});
});
setTimeout(function () {
console.log('setTimeout2');
});
//1 2 s1 p s2
操作系统知识回顾:一个进程包含多个线程,进程中的内存空间允许线程们共享,但达到上限时需要线程排队…
arguments
简单说一下伪数组,满足伪数组的3点要求:
- 是对象。
- 必须有length属性,且值是number类型。
- 如果这个对象的length不为0,那么必须要有按照下标存储的数据。
常见伪数组:
- document.getElementByTagname();返回来的NodeList对象。
- function(){};自带的arguments对象。
- 自定义的具有length等满足要求的对象。
var fakeArr = {
name: 'hyt',
hobby: 'badminton',
length: 2
}
//检测伪数组的方式:
fakeArr instanceof Array; false;
//使伪数组具有数组的方法的方式:
Array.protptye.slice.call(fakeArr);
function中自带的arguments参数也是伪数组,arguments包含了函数中的参数数组,arguments对象中(它确实也是对象)还带有callee属性,callee属性是一个指针,指向拥有这个arguments对象的函数。
我们可以利用arguments下的callee写递归函数(阶乘函数):
function recursive(num) {
if(num <= 1) {
return num;
} else {
return num * arguments.callee(num-1);
}
}
//使用arguments.callee代替函数名,降低了耦合性,更加灵活。
简述promise
当我们请求来的数据们需要相关联使用时,我们也许会使用以下这种方式发到目的:
$.ajax({
data: {
id: id
}
})
.done(function(response) {
$.ajax({
data: {
id: response.id
}
}).done( function(res){})........
})
这种多层嵌套方法的写法又叫邪恶金字塔,是十分不友好的,而pormise()就可以避免这种情况。
promise基本概念
- promise只有三种状态,未完成,完成(fulfilled)和失败(rejected)。
- promise的状态可以由未完成转换成完成,或者未完成转换成失败。
- promise的状态转换只发生一次 promise的状态可以由未完成转换成完成,或者未完成转换成失败。
promise有一个then方法,then方法可以接受3个函数作为参数。前两个函数对应promise的两种状态fulfilled, rejected的回调函数。第三个函数用于处理进度信息。
var promise = new Promise();
promise.then(function(){},function(){},function(){})
练习:使用promise()封装一个ajax
function ajaxFn(url,method) {
var promise = new Promise(function(resolve,reject){
var xhr = new XMLHttpRequest();//创建一个xhr对象;
xhr.
xhr.onreadystatechange = function() {
if (readystate == 4) {
if(xhr.status >=200 && xhr.status <300 || xhr.status == 304) {
//状态码200为成功,304 not modify读取浏览器缓存文件;
resolve(xhr.response);
} else {
reject(xhr.responseText);
}
}
};
xhr.open(url,method);//打开服务器地址,与服务器交互;
xhr.responseType = 'json';
xhr.send(null);
return promise;
});
};
ajaxFn('aaa.bb.com','get').then(function(data){
console.log(data);
},function(errmsg){
console.error(errmsg);
})
//ajax实现思路:open()启动发送请求的准备,send()发送请求,readyState表示响应过程的当前活动阶段,4代表成功,readyState每变化一次会触发onreadystatechange()事件,所以以利用onreadystatechange事件来监听readystate的状态。为了兼顾浏览器的兼容问题,需要将onreadystatechange事件卸载open()方法之前。
//当发送多个请求中途需要砍掉有些请求时:xhr.abort()方法,可以终止xhr.send()的发送。
//url必须符合同源策略
关于Cookie
众所周知,HTTP 是一个无状态协议,所以客户端每次发出请求时,下一次请求无法得知上一次请求所包含的状态数据,如何能把一个用户的状态数据关联起来呢?
比如在淘宝的某个页面中,你进行了登陆操作。当你跳转到商品页时,服务端如何知道你是已经登陆的状态?
cookie
首先产生了 cookie 这门技术来解决这个问题,cookie 是 http 协议的一部分,它的处理分为如下几步:
服务器向客户端发送 cookie。
通常使用 HTTP 协议规定的 set-cookie 头操作。
规范规定 cookie 的格式为 name = value 格式,且必须包含这部分。
浏览器将 cookie 保存。
每次请求浏览器都会将 cookie 发向服务器。
其他可选的 cookie 参数会影响将 cookie 发送给服务器端的过程,主要有以下几种:
path:表示 cookie 影响到的路径,匹配该路径才发送这个 cookie。
expires 和 maxAge:告诉浏览器这个 cookie 什么时候过期,expires 是 UTC 格式时间,maxAge 是 cookie 多久后过期的相对时间。当不设置这两个选项时,会产生 session cookie,session cookie 是 transient 的,当用户关闭浏览器时,就被清除。一般用来保存 session 的 session_id。
secure:当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效。
httpOnly:浏览器不允许脚本操作 document.cookie 去更改 cookie。一般情况下都应该设置这个为 true,这样可以避免被 xss 攻击拿到 cookie。