1. js中的继承
1.1 为什么要学习js中的继承
此时我们希望son对象,也拥有far对象的money, house这些属性,但是我们不想再重新写一遍,怎么办?
var far = {
money : $1000000000,
house : ['别墅', '大别墅', '有游泳池的打别墅', '城堡'];
}
var son = {
}
1.2 两个对象之间的继承
1.2.1 遍历对象
var far = {
wife : '小龙女',
money : 1000000000,
house : ['别墅', '大别墅', '有游泳池的打别墅', '城堡']
}
var son = {
wife : '小黄飞',
}
//通过for..in遍历far
for(var key in far){
son[key] = far[key];
}
console.log(son); // 此时son拥有了money和house .
但是以上代码书写存在一个问题 : 把wife的值也继承下来了.我们希望自己有的,就不继承了.所以我们要改一下代码
for(var key in far){
//自己没有的才继承
if(!son.hasOwnProperty(key)){
son[key] = far[key];
}
}
这样处理解决刚才的问题,但是我们发现两个对象上面存在一样的代码.这样比较占用内存
1.2.2 通过create方法实现继承
通过ceate方法实现继承,可以避免遍历对象方式,占用内存的问题
create方法语法: Object.create(参考对象) 返回一个新的对象
create方法的作用: 返回的新的对象的__proto__
指向参考对象
var far = {
wife : '小龙女',
money : 1000000000,
house : ['别墅', '大别墅', '有游泳池的打别墅', '城堡']
}
var son = Object.create(far);
son.wife = '小黄飞';
console.log(son.money); //1000000000
console.log(son.house); //['别墅', '大别墅', '有游泳池的打别墅', '城堡']
console.log(son.wife); // 小黄飞
console.log(son.__proto__ === far); //true
1.3 构造函数的继承
我们工作中要经常创建多个具有相同属性的对象,所以经常要写构造函数.
那么构造函数创建的出来的对象该如何实现继承呢?
1.3.1 借用构造函数(继承私有属性)
function Person(name, age){
this.name = name;
this.age = age;
this.sayHello = function(){
console.log('hello, ' + '我是' + this.name );
}
}
function Student(name, age, score){
this.score = score;
Person.call(this, name, age);//借用构造函数
}
var stu = new Student('zs', 18, 100);
console.log(stu); //{score : 100, name : zs, age : 18}
stu.sayHello(); //hello, 我是zs
但是我们一般不会把方法写在构造函数的函数体内,我们把方法写在函数的原型上,那么这个时候构造借用函数就无法继承方法了,我们想要继承原型上的方法.可以用原型继承。
1.3.2 原型继承(继承公有属性)
用来继承方法
function Person(name, age){
this.name = name;
this.age = age;
this.sayHello = function(){
console.log('hello, ' + '我是' + this.name );
}
}
function Student(name, age, score){
this.score = score;
}
Student.prototype = new Person();
var stu = new Student('zs', 18, 100);
console.log(stu); //{score : 100}
stu.sayHello(); //hello, 我是undefined
我们发现,方法继承下来了,但是属性却没有继承下来
1.3.3 组合继承
借用构造函数 + 原型继承
function Person(name, age){
this.name = name;
this.age = age;
this.sayHello = function(){
console.log('hello, ' + '我是' + this.name );
}
}
function Student(name, age, score){
this.score = score;
Person.call(this, name, age);
}
Student.prototype = new Person();
var stu = new Student('zs', 18, 100);
console.log(stu); //{score : 100, name : zs, age : 18}
stu.sayHello(); //hello, 我是zs
2.栈和堆
理解JavaScript中的堆和栈:https://www.cnblogs.com/guchengnan/p/9406607.html
说到堆栈,我们讲的就是内存的使用和分配了,没有寄存器的事,也没有硬盘的事。
各种语言在处理堆栈的原理上都大同小异。堆是动态分配内存,内存大小不一,也不会自动释放。栈是自动分配相对固定大小的内存空间,并由系统自动释放。
javascript的基本类型就5种:Undefined、Null、Boolean、Number和String,它们都是直接按值存储在栈中的,每种类型的数据占用的内存空间的大小是确定的,并由系统自动分配和自动释放。这样带来的好处就是,内存可以及时得到回收,相对于堆来说,更加容易管理内存空间。
javascript中其他类型的数据被称为引用类型的数据 : 如对象(Object)、数组(Array)、函数(Function) …,它们是通过拷贝和new出来的,这样的数据存储于堆中。其实,说存储于堆中,也不太准确,因为,引用类型的数据的地址指针是存储于栈中的,当我们想要访问引用类型的值的时候,需要先从栈中获得对象的地址指针,然后,在通过地址指针找到堆中的所需要的数据。
说来也是形象,栈,线性结构,后进先出,便于管理。堆,一个混沌,杂乱无章,方便存储和开辟内存空间
3.事件轮询机制
v8引擎 : 执行js代码的地方(堆,栈)
webapi : 帮助js计时
事件轮询: 会一直观察栈和任务队列,并且会把任务对列中的任务拿到栈中执行
任务队列: 存储定时器事件到了之后/触发了之后的事件处理函数.
- 1.深入浅出js事件循环机制(上)zhuanlan.zhihu.com/p/26229293
- 2.深入浅出js事件循环机制(下)zhuanlan.zhihu.com/p/26238030
小结
上面的这一个流程解释了当浏览器遇到setTimeout之后究竟是怎么执行的,相类似的还有前面图中提到的另外的API以及另外一些异步的操作。
总结上文说的,主要就是以下几点:
- 所有的代码都要通过函数调用栈中调用执行。
- 当遇到前文中提到的APIs的时候,会交给浏览器内核的其他模块进行处理。
- 任务队列中存放的是回调函数。
- 等到调用栈中的task执行完之后再回去执行任务队列之中的task。
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
}
console.log(new Date, i);
-
首先i=0时,满足条件,执行栈执行循环体里面的代码,发现是setTimeout,将其出栈之后把延时执行的函数交给Timer模块进行处理。
-
当i=1,2,3,4时,均满足条件,情况和i=0时相同,因此timer模块里面有5个相同的延时执行的函数。
-
当i=5的时候,不满足条件,因此for循环结束,console.log(new Date, i)入栈,此时的i已经变成了5。因此输出5。
-
此时1s已经过去,timer模块将5个回调函数按照注册的顺序返回给任务队列。
-
执行引擎去执行任务队列中的函数,5个function依次入栈执行之后再出栈,此时的i已经变成了5。因此几乎同时输出5个5。
-
因此等待的1s的时间其实只有输出第一个5之后需要等待1s,这1s的时间是timer模块需要等到的规定的1s时间之后才将回调函数交给任务队列。等执行栈执行完毕之后再去执行任务对列中的5个回调函数。这期间是不需要等待1s的。因此输出的状态就是:5 -> 5,5,5,5,5,即第1个 5 直接输出,1s之后,输出 5个5;
4. 函数的静态成员和实例成员
成员: 泛指属性和方法
静态成员: 函数也是对象, 函数自己的属性
实例成员: 特指构造函数时,写在构造函数体内,通过this.xxx.给实例添加的属性
function Person(){
this.name = name; //实例成员
this.age = age; // 实例成员
}
Person.type = '呵呵'; //静态成员
5.Function的原型图
Functon 自己创造了自己
Object是Function的实例
所有的函数,底层都是new Function()
<script>
// function fn(x, y){
// console.log(x+y);
// } // var fn = new Function();
// var fn = function (){
//
// } // var fn = new Function();
// var fn = new Function('x','y','console.log(x+y)');
// fn(3,5);
function fn(){
}
console.log(fn.__proto__);
console.log(fn.__proto__.__proto__);
console.log(Function.__proto__.constructor);
console.log(Object.__proto__.constructor);
</script>
6. 闭包
概念: 函数和函数作用域的结合
通俗理解: 内部函数使用外部函数的变量, 整个外部函数形成了一个闭包
6.1闭包的作用:
-
私有化数据
-
数据保持
function main(){
var money = 10000; //放到局部作用中,防止全局变量污染(私有化数据)
return {
queryMoney : function(){
return money;
},
payMoney : function(num){
money -= num;
},
addMoney : function(num){
money += num;
}
}
}
var moneyManger = main(); // 通过moneyManger 可以获取到局部的变量money
6.2闭包的缺点:
由于内部的函数使用了外部函数的变量,导致外部这个函数无法被回收掉.如果代码中大量的存在闭包,可能会导致内存泄露 (不要刻意使用闭包);
6.3闭包案例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
<li>第1个li</li>
<li>第2个li</li>
<li>第3个li</li>
<li>第4个li</li>
<li>第5个li</li>
<li>第6个li</li>
</ul>
<script>
//不能使用this, 利用闭包,打印每一个li的文本
var lis = document.getElementsByTagName('li');
// for(var i = 0; i < lis.length; i++) {
// lis[i].onclick = function(){
console.log(this.innerText);
// console.log(lis[i].innerText);
// }
// }
for(var i = 0; i < lis.length; i++) {
(function(i){
lis[i].onclick = function(){
// console.log(this.innerText);
console.log(lis[i].innerText);
}
})(i);
}
</script>
</body>
</html>
7. 面向对象编程
// 面向过程的思想: 一步一步的具体去做这个事情
// 面向对象的思想: 需要哪个对象,调用这个对象的某个方法,帮助我们实现具体的需求'
// 面向对象是对面向过程的封装
// 面向对象编程的好处: 1. 维护起来更方便 2.团队协作更加的便捷
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
// 面向过程的思想: 一步一步的具体去做这个事情
// 面向对象的思想: 需要哪个对象,调用这个对象的某个方法,帮助我们实现具体的需求'
// 面向对象是对面向过程的封装
// 面向对象编程的好处: 1. 维护起来更方便 2.团队协作更加的便捷
// 面向过程:
// function max(x,y){
// if(x > y){
// return x;
// }else{
// return y;
// }
// }
// function max(){
arguments 是函数中的一个对象,而 arguments是一个伪数组,里面存储了所有传进来的实参
console.log(arguments);
// var max = 0; //用于和数组每一项做比较,如果数组中的值更大,则把数组中的值赋值给max
// for(var i = 0; i < arguments.length; i++) {
// if(arguments[i]>max){
// max = arguments[i];
// }
// }
// return max;
// }
//
//console.log(max(1, 2, 3, 4, 4, 5, 65, 6));;
// 面向对象: 找对象,调用对象的方法
console.log(Math.max(3, 6, 9, 12, 89));
</script>
</body>
</html>
8. 贪吃蛇案例
9.JavaScript中的伪数组对象转化成数组的方法
转自: https://blog.csdn.net/m0_38102188/article/details/81327465
首先什么是伪数组对象呢?
字面意思理解来说,伪数组对象就是长得像数组的对象,拥有0,1,2,3…以及length等key,但是在原型链中没有Array.prototype这一环,所以他没有操作数组的方法push,pop,shift,unshift等。javaScript中常见的伪数组对象有以nodeList为原型的DOM对象,函数的参数arguments对象等。
那么有没有方法将这些伪数组对象转换成数组,从而直接使用push,pop等方法来对其进行操作呢?
答案是肯定的。
var divTags = document.querySelectorAll('div')
ES5提供了一种方法:
var divTagsArray = Array.prototype.slice.call(divTags)
console.log('push' in divTagsArray) //true
ES6提供了两种方法:
let divTagsArray1 = Array.from(divTags)
console.log('push' in divTagsArray1) //true
这里的divTags是部署 Iterator 接口的数据类型,所以也可以用以下方式实现转换:
let divTagsArray2 = [...divTags]
console.log('push' in divTagsArray2) //true