上一篇:20.JS高级(一)
JavaScript学习
四、堆栈
栈内存和堆内存 (c语言、java)
在js当中所有的内存都属于堆内存 然后在堆内存内部又分为栈结构和堆结构
栈(stack):栈会自动分配内存空间,会自动释放,存放基本类型和引用数据类型的变量
所有在方法中定义的变量都是放在栈内存中,随着方法的执行结束,这个方法的内存栈也自然销毁。
优点:存取速度比堆快。
缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
队列
先进先出 后进后出
堆(heap):动态分配的内存,大小不定也不会自动释放,存放引用类型的对象,指那些可能由多个值构成的对象,保存在堆内存中。 无序的
堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(参数传递)。创建对象是为了反复利用。
1. 基本数据类型使用
var a = 10;
var b = a;
b = 20;
console.log(a); // 10值
console.log(b); // 20值
下图演示了这种基本数据类型赋值的过程:
b获取的是a值得一份拷贝,虽然,两个变量的值相等,但是两个变量保存了两个不同的基本数据类型值。
b只是保存了a复制的一个副本。所以,b的改变,对a没有影响。
2. 引用数据类型使用
引用类型是存放在堆内存中的对象,变量其实是保存的在栈内存中的一个指针(保存的是堆内存中的引用地址),这个指针指向堆内存。
引用类型数据在栈内存中保存的实际上是对象在堆内存中的引用地址。通过这个引用地址可以快速查找到保存中堆内存中的对象
var obj1 = new Object();
var obj2 = obj1;
obj2.name = "我有名字了";
console.log(obj1.name); // 我有名字了
说明这两个引用数据类型指向了同一个堆内存对象。obj1赋值给obj2,实际上这个堆内存对象在栈内存的引用地址复制了一份给了obj2,但是实际上他们共同指向了同一个堆内存对象,所以修改obj2其实就是修改那个对象,所以通过obj1访问也能访问的到。
3. 案例练习
如果是基本数据类型,赋值的时候直接赋的就是基本的数据值,而且是直接存储在栈里
如果是对象数据类型,把它赋值给变量的时候,赋值的是这个对象数据的地址
-
var arr = [1,2,3]; var arr1 = arr; arr[1] = 22; console.log(arr); // [1,22,3] console.log(arr1); //[1,22,3]
-
var arr = [1,2,3]; var arr1 = arr; arr = [1,22,3]; console.log(arr1); // [1,2,3]
基本数据传参和对象数据传参区别
-
基本数据传参 var a = 10; var b = 20; function add(a,b){ a = 40; return a + b; } console.log(add(a,b)); //60 console.log(a,b); // 10,20
-
对象数据类型传参 var arr = [1,2,3]; function fn(arr){ for(var i = 0; i < arr.length; i++){ arr[i] += 2; } } fn(arr); console.log(arr); // [3,4,5]
总结:
1、程序一开始执行,碰见了全局环境,首先会创建全局环境并且进行压栈,全局代码执行的时候依赖的就是全局环境当中的东西;比如 全局变量(全局变量如果存的是基本数据类型,那么这个值是直接存在栈当中的,如果这个变量存的是对象类型(函数、数组),那么数据是要在堆结构当中开辟自己的空间专门存储的。然后把堆里面这块空间的地址存给栈当中的对应变量);
2、当程序执行碰到了函数调用;函数是比较特殊,因为它也可以执行;函数执行的时候也要有自己的环境去依赖。因此函数执行也是创建自己的函数环境进行压栈(函数执行环境一定是压在全局执行环境之上的),局部变量,是在函数环境当中存在的,只有函数执行,局部变量才会出现。函数执行完成以后,函数环境要弹出栈(销毁归还内存),局部变量也就不复存在了。
3、当函数调用完成以后,会继续执行全局代码,一直到所有的代码都执行完成,代表程序执行结束,程序结束的时候,我们的全局环境最后出栈。
4. 面试题讲解
-
var num1 = 10; var num2 = num1; num1 = 20; console.log(num1); //20 console.log(num2); //10
-
var num = 50; function f1(num) { num = 60; console.log(num); //60 } f1(num); console.log(num); //50
-
var num1 = 55; var num2 = 66; function f1(num, num1) { num = 100; num1 = 100; num2 = 100; console.log(num); //100 console.log(num1); //100 console.log(num2); //100 } f1(num1, num2); console.log(num1); //55 console.log(num2); //100 console.log(num); //报错
-
var a = 10; var b = 20; function add(a,b){ a = 30; return a + b; // 50 } add(a,b); console.log(a); // 10 function f1(arr){ for(var i = 0; i < arr.length; i++){ arr[i] += 2 } console.log(arr); //[3,4] } var arr; arr = [1,2]; f1(arr); console.log(arr); //[3,4]
-
两个对象是同一个对象,不同的操作有什么不同 var a = [1,2]; var b = a; a[0] = 20; // 如果a = [20,2]; console.log(b);// [20,2] var a = [];//new Array() var b = [];//new Array() console.log(a == b); //false
-
function Person(name, age, salary) { this.name = name; this.age = age; this.salary = salary; } function f1(pp) { pp.name = "ls"; pp = new Person("aa", 18, 10); } var p = new Person("zs", 18, 1000); console.log(p.name);//zs f1(p); console.log(p.name);//ls
5. new关键字的作用
function Dog(name,age,color){
this.name = name;
this.age = age;
this.color = color;
this.eat = function(){
console.log('吃肉');
}
}
var result = Dog('小黄',3,'yellow');
var d1 = new Dog('小黑',2,'black');
1、开辟内存空间(堆)
2、this指向该内存(让函数内部的this)
3、执行函数代码
4、生成对象实例返回(把空间的地址返回)*
// 构造函数当做普通函数来使用
function Person(name,age){
this.name = name;
this.age = age;
}
// 函数如果没有返回值 则返回的是undefined
var p = Person('zs',18);
console.log(p);
// 06_new关键字的作用-构造函数当做普通函数-构造函数中有方法
function Person(name,age){
this.name = name;
this.age = age;
this.eat = function(){
console.log('猪肚鸡');
}
console.log(this);//window
}
var p = Person('zs',18);
console.log(p);//undefined
function Person(name,age){
this.name = name;
this.age = age;
this.eat = function(){
console.log('椰子鸡');
}
}
var p = new Person('阿马',35);
console.log(p);//person{}
一、call和apply方法
1. 概念
任何函数对象都有apply和call方法
apply和call可以使用第一个参数传对象,让函数或者方法的执行者(this)指向这个对象;
2. 语法
call和apply可以让一个对象执行另外一个对象的方法;
函数或者方法.apply(对象,[函数的参数]);
函数或者方法.call(对象,函数的参数1,函数的参数2);
3. 作用
call和apply做了两件事:
1、调用的时候先把this指向改为你指定的第一个参数对象
2、然后再去自动执行使用的方法
4. 案例
- 没有参数
var obj = {
name:'马坚强',
age:33
}
function Dog(name,age){
this.name = name;
this.age = age;
this.eat = function(){
console.log('吃肉');
}
}
//Dog.prototype.eat = function(){
// console.log('吃肉');
// }
var d1 = new Dog('旺财',3);
d1.eat();
obj.eat(); // 思考报错原因
-
解决策略
-
给这个对象自己添加一个吃肉的方法 obj.eat = function(){ console.log('吃肉'); } obj.eat();
-
不给这个对象添加,而是使用apply或者call借用 狗的吃肉方法去实现 d1.eat.apply(obj); //把狗的eat方法调用时的this指向修改为obj,代表obj在执行狗的这个方法 d1.eat.call(obj);
-
-
有参数
var obj = ( name:'haha', age:12 ) Dog.prototype.eat = function(a,b){ console.log(this); console.log('吃肉'); console.log(a,b); } var d1 = new Dog('旺财',3); d1.eat(10,20);//10,20 d1.eat.apply(obj,[100,200]); d1.eat.call(obj,100,200);
call传达的是值,apply传达的是数组
var obj = {}; var objAdd = { add:function(a,b){ return a + b; } } var sum = objAdd.add(1,2); console.log(sum); //3 // call方法传递的参数 一个又一个的值 而非数组 var sumCall = objAdd.add.call(obj,1,5); console.log(sumCall); //6 // apply方法传递的参数是一个数组 var sumApply = objAdd.add.apply(obj,[1,7]); console.log(sumApply); //8
二、bind方法
bind() 函数会创建一个新函数(称为绑定函数)
- bind是ES5新增的一个方法
- 传参和call类似
- 不会执行对应的函数,而是返回了一个方法体,call或apply会自动执行对应的函数
- 返回对函数的引用
- 语法
fun.bind(thisArg[, arg1[, arg2[, ...]]])
var obj = {
name: "阿马"
}
function add(a, b) {
console.log(this);
console.log(a + b)
}
var new = add.bind(obj, 1, 2)
console.log(new);//3
new();
//返回的不是同一个函数
console.log(new === add); //false
案例练习:找到数组中最大的数值
<script>
var arr = [1,2,3];
// console.log(Math.max(...arr));
console.log(Math.max.apply(Math, arr));
</script>
三、变量的作用域
3.1 变量作用域
变量作用域(scope)是指变量在程序中可以访问的有效范围,也称为变量的可见性。分为全局变量和局部变量
- 全局变量:变量在整个页面中都是可见的,可以被自由的访问
- 局部变量,变量仅能在声明的函数内部可见,函数外是不允许访问的。
3.2 JavaScript 局部变量
变量在函数内声明,变量为局部作用域。
局部变量:只能在函数内部访问。
// 此处不能调用 name 变量
function myFunction() {
var name = "red romance";
// 函数内可调用 name 变量
}
因为局部变量只作用于函数内,所以不同的函数可以使用相同名称的变量。
局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。
3.3 JavaScript 全局变量
变量在函数外定义,即为全局变量。
全局变量有全局作用域: 网页中所有脚本和函数均可使用。
var name = "zhangsan";
// 此处可调用 name 变量
function myFunction() {
// 函数内可调用 name 变量
console.log(name);
}
特别需要注意:如果变量在函数内没有声明(没有使用 var 关键字),该变量为全局变量。但是如果该变量出现在形式参数中,则为局部变量。
以下实例中 name在函数内,但是为全局变量。
function myFunction() {
name = "lisi";
// 此处可调用 name 变量
}
myFunction();
console.log(name);
当函数内部写了一个变量不带var 有以下几种情况:
1、如果函数内部出现不带var的变量,那么首先找形参;如果形参有,当做是局部变量对待
2、如果函数内部出现不带var的变量,形参也没有这个变量,那么就去找外部全局看有没有,有就是函数内部在操作全局变量
3、如果函数内部出现不带var的变量,形参也没有这个变量,全局也没有这个变量;那么此时这个变量相当于在全局定义了一个变量;
3.4 作用域链
作用域链:JS中数据的查找规则。
作用域链和作用域不是一回事;
作用域描述是变量起作用的区域和范围
作用域链描述的程序在找变量的过程;
程序在查找变量的时候,先从自己的作用域去查找,如果找到就直接使用这个变量的值,如果没有找到,会继续往上一级作用域去找,同样也是找到就使用没有找到继续往上找;直到找到全局作用域,如果找到就使用,找不到就报错(引用错误,这个变量没有定义);
<script>
var a = 2;
function test(){
var a = 1;
console.log(a);
}
test();
</script>
<script>
function fn(){
function fn2(){
var a = 10;
console.log(a);
}
fn2();
}
fn();
</script>
案例练习:经典案例。
<script>
var a = 0;
function fn1() {
var a = 1;
function fn2() {
var a = 2;
function fn3() {
// var a = 3;
console.log(a);
}
fn3();
}
fn2();
}
fn1();
</script>
作用域不是在函数调用的时候确定的,而是在函数定义的时候就确定好了
3.5 闭包
要理解闭包,首先必须理解Javascript特殊的变量作用域。
变量的作用域无非就是两种:全局变量和局部变量。
- JavaScript语言的特殊之处,就在于函数内部可以直接读取全局变量。
var n=999;
function f1(){
alert(n);
}
f1();
- 在函数外部自然无法读取函数内的局部变量
function f1(){
var n=999;
}
alert(n);
- 函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量
function f1(){
n=999;
}
f1();
alert(n);
-
如何从外部读取局部变量?
出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。
那就是在函数的内部,再定义一个函数。
function f1(){
var n=999;
function f2(){
alert(n); // 999
}
}
在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!
function f1(){
var n=999;
function f2(){
alert(n);
}
//返回一个f2的方法体
return f2;
}
var result=f1();
result(); // 999
-
闭包的概念
代码中的f2函数,就是闭包。
闭包"(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
-
如何产生闭包(条件)?
- 函数嵌套
- 内部函数引用外部函数的局部变量
- 使用(调用)外部函数并且内部函数也要调用或者引用(针对谷歌)(因为部分浏览器会对内部函数做优化,内部函数不使用或者不引用,相当于没有);
-
闭包到底是什么?
- 理解一: 闭包是嵌套的内部函数(绝大部分人)
- 理解二: 包含被引用(外部函数)变量的对象(极少数人)
- 理解三: 所谓的闭包是一个引用关系,该引用关系存在于内部函数中,引用的是外部函数的变量的对象(深入理解)
-
常见的闭包
- 将函数作为另一个函数的返回值
-
闭包的作用
function fn(){ var a = 0; function fn1(){ a++; console.log(a); } return fn1; } var f = fn(); f(); f();
- 延长外部函数变量对象的生命周期
- 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
- 注意: 浏览器为了性能后期将外部函数中不被内部函数使用的变量清除了
-
闭包的缺点和解决(内存泄漏和内存溢出)
- 内存泄漏 : 内存无法释放;
- 内存溢出 : 内存被撑爆;
- f = null; 解决方式;
function fn(){ var a = 0; function fn1(){ a++; console.log(a); } return fn1; } var f = fn(); f(); f(); f = null;//释放闭包
-
使用闭包的注意点
-
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
-
闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
虽然我们可以获取内部变量 但是不要轻易修改这个内部变量。
-
-
面试题讲解
-
面试题1
var name = "The Window"; var object = { name: "My Object", getNameFunc: function() { return function() { return this.name; }; } }; console.log(object.getNameFunc()());
-
面试题2
var name2 = "The Window"; var object2 = { name2: "My Object", getNameFunc: function () { //如果说你想在内部其它函数当中使用外部函数的this,就把外部函数this保存起来 var that = this; return function () { return that.name2; }; } }; console.log(object2.getNameFunc()());
-
四、执行上下文、执行上下文栈、预解析、作用域、作用域链
-
执行上下文(执行上下文环境):
- 程序在解析和运行的时候所依赖和使用的环境;
- 全局执行上下文环境 和 函数执行上下文环境 (全局执行环境和函数执行环境)
-
执行上下文栈:
- 程序为了管理执行上下文(确保程序的执行顺序)所创建的一个栈数据结构,被称作执行上下文栈;
-
预解析(变量提升):
-
概念:
- 预解析分为变量预解析(变量提升)和函数预解析(函数提升)
-
说明:
- 程序在开始执行之前会做全局代码预解析
- 函数在开始执行之前也会做局部代码预解析
-
特点
-
程序在代码执行之前会先进行预解析;
-
预解析会解析带var的变量和函数声明定义的函数function,解析函数优先级比解析变量要高:
-
可以认为解析的时候分为两步,先去解析所有的函数声明定义的函数,再去解析所有的带var变量;
-
解析过程当中,如果函数重名,会覆盖(后面的函数会把前面的覆盖掉)
如果变量重名,会忽略;
-
解析函数的时候,函数定义方式不同,解析过程也不大一样:
如果是函数声明定义,整个函数都要被提升
function func(){};
如果是表达式定义,只会提升变量
var func = function(){};
-
-
先解析函数:函数重名覆盖
-
再解析变量:变量重名忽略
-
-
作用域
- 变量起作用的范围;
- 作用域;隔离变量,防止变量命名污染;
-
作用域链
- 真实存在的,作用域链是使用执行上下文当中变量对象所组成的链条结构(数组结构),查找的时候其实真正是先去自身的变量对象当中查找,如果没有,去上级执行上下文的变量对象当中去查找,直到找到全局执行上下文的变量对象; 函数上一级的变量对象其实是在函数定义的时候都已经确定好的
-
全局执行上下文(分为创建阶段和执行阶段)代码开始执行之前和之后
-
全局执行上下文压入执行上下文栈
-
创建上下文阶段:
-
收集变量形成变量对象 函数 var的变量会收集,预解析(其实在创建变量对象的时候已经做了预解析)
-
确定this指向(可以认为确定执行者)
-
创建自身执行上下文的作用域链
注意:同时确定函数在调用时候的上级作用域链。(根据ECMA词法去确定,看内部是否引用外部变量确定)
-
-
-
执行全局执行上下文
- 执行全局上下文阶段
- 为变量真正赋值
- 顺着作用域链查找要使用的变量或者函数执行
- 执行全局上下文阶段
-
-
函数执行上下文
-
函数执行上下文压栈
-
收集变量 var 形参 arguments 函数
-
确定this指向(可以认为确定执行者)
-
创建自身执行上下文的作用域链(它是拿自己的变量对象和上一级的作用域链组成自己的作用域链)
注意:同时确定函数在调用时候的上级作用域链。(根据ECMA词法去确定,看内部是否引用外部变量确定
- 函数的作用域链: 自己定义的时候已经确定了函数在调用时候的上级作用域链,因此,在函数调用的时候,只需要将自己的变量对象添加到上级作用域链的顶端;就形成自己的作用域链
-
-
执行函数执行上下文
- 为变量真正赋值
- 顺着作用域链查找要使用的变量或者函数执行
-
-
面试题讲解
-
alert(a); //报错 a = 0;
-
alert(a); //undefined var a = 0; alert(a);
-
alert(a); //{ alert('我是函数') } var a = '我是变量'; function a(){ alert('我是函数') } alert(a); //我是变量
-
alert(a); a++; alert(a); var a = '我是变量'; function a(){ alert('我是函数') } alert(a)
-
alert(a); var a = 0; alert(a); function fn(){ alert(a); var a = 1; alert(a); } fn() alert(a);
-
alert(a); //undefined var a = 0; alert(a); //0 function fn(){ alert(a); //0 a = 1; alert(a); //1 } fn() alert(a); //1
总结:预解析先去解析函数声明定义的函数,再去解析带var的变量;
函数重名会覆盖,变量重名会忽略;(变量如果不带var,变量是不会进行预解析的;只有带var的变量才会进行预解析;表达式定义的函数也是当做变量去解析)
-
五、JavaScript原型对象
思考:下列代码有什么弊端?
function Villa(size,styleType,price){
this.size = size;
this.styleType = styleType;
this.price = price;
this.live = function(){
console.log('住的很舒服');
}
}
var v1 = new Villa(1000,'新中式',700);
v1.live();
console.log(v1.styleType);
var v2 = new Villa(2000,'简欧',1000);
v2.live();
console.log(v2.styleType);
这个live是个方法, 每个实例化对象内部到时候都会有单独的自己的这个方法
如果实例化的对象多了, 那么就会造成内存浪费很厉害
因为每个对象去调用自己的这个方法, 最终实现的功能是一样的;
所以我们得想办法让所有的实例化对象去调用同一个方法。 以达到资源共享, 节省内存的目的;
5.1 什么是原型对象
所有 JavaScript 对象都从原型继承属性和方法。
日期对象继承自 Date.prototype。数组对象继承自 Array.prototype。Person 对象继承自 Person.prototype。
Object.prototype 位于原型继承链的顶端:
日期对象、数组对象和 Person 对象都继承自 Object.prototype。
- 显式原型对象
原型对象就是函数对象的一个属性prototype的值( 地址),这个prototype属性值是原型对象, 也被叫做显式原型对象.ES5中适合在原型内部添加东西—修改。
console.dir(Villa);
- 隐式原型对象
由这个函数实例化出来的对象身上都会有一个属性叫_proto_, 它和函数对象prototype地址一样, 代表同一个对象,如果我们通过__proto__去操作原型对象, 称为隐式原型对象。ES5中适合查看原型。
var v1 = new Villa(1000,'中式',10000000);
console.log(v1);
5.2 原型对象的格式
function Villa(size, styleType, price) {
this.size = size;
this.styleType = styleType;
this.price = price;
// this.live = function(){
// console.log('住的很舒服');
// }
}
Villa.prototype.live = function() {//显式原型对象
console.log('住的很舒服');
} //把方法添加在原型对象当中,让所有的实例化对象共享
var v1 = new Villa(1000, '新中式', 10000000);
v1.live();
console.log(v1.styleType);
var v2 = new Villa(2000, '简欧', 20000000);
v2.live();
console.log(v2.styleType);
5.3 原型链
描述的是对象在查找属性或者方法的过程
实例化对象在找属性的时候,先从自身去找看有没有这个属性,如果有,直接使用这个属性的值,如果没有,会继续顺着这个对象的隐式原型对象(_proto_)找到这个对象的原型对象(和它的构造函数的显式原型对象是同一个),看看原型对象是否存在这个属性,如果有就使用原型对象当中的这个属性值,如果还没有,再去找原型对象的隐式原型对象(默认就是Object显式原型对象),找到以后去看看有没有这个属性,如果有就使用这个属性值;如果没有就返回undefined(代表已经找到顶了);
var obj = {
name:'马大哥',
age:33
}
function Dog(name,age){
this.name = name;
this.age = age;
}
Object.prototype.eat = function(){
console.log('吃肉');
}
var d1 = new Dog('旺财',3);
d1.eat(10,20);
obj.eat();
总结:
那什么是原型链呢?
简单理解就是原型组成的链,对象的__proto__它的是原型,而原型也是一个对象,也有__proto__属性,原型的__proto__又是原型的原型,就这样可以一直通过__proto__想上找,这就是原型链,当向上找找到Object的原型的时候,这条原型链就算到头了。
原型对象和实例之间有什么关系呢?
通过一个构造函数创建出来的多个实例,如果都要添加一个方法,给每个实例去添加并不是一个明智的选择。这时就该用上原型了。
在实例的原型上添加一个方法,这个原型的所有实例便都有了这个方法。
5.4 终极原型链
六、面向对象(原型继承、构造函数继承、组合继承)
面向对象三大特性:封装继承多态
- 原型继承
- 让父类的实例作为子类的原型,将子类的原型构造器补充完整 (为了让子类继承方法)
<script type="text/javascript">
//父类
function Dog(name,age){
this.name = name;
this.age = age;
}
//子类
function Teddy(name,age){
this.name = name;
this.age = age;
}
Dog.prototype.run = function(){
console.log('跑的很快~');
}
//为了让子类去继承父类原型当中的方法
//我们需要用到原型继承,原型继承主要就是为了让子类继承方法使用的
//让子类的原型变成父类的一个实例
Teddy.prototype = new Dog();
// 手动给原型对象添加一个构造器,因为原型对象里面都是有构造器的,指向和自己匹配的构造函数
Teddy.prototype.constructor = Teddy;
var d1 = new Dog('旺财',3);
d1.run();
var t1 = new Teddy('小黑',2);
t1.run(10);
</script>
- 借用构造函数继承
- 在子类当中去调用父类的构造函数(为了让子类继承属性)
<script type="text/javascript">
//父类
function Dog(name,age){
this.name = name;
this.age = age;
}
//子类
function Teddy(name,age){
// this.name = name;
// this.age = age;
//借助父类的构造函数实现属性的继承
Dog.call(this,name,age);
}
Dog.prototype.run = function(){
console.log('跑的很快~');
}
//为了让子类去继承父类原型当中的方法
//我们需要用到原型继承,原型继承主要就是为了让子类继承方法使用的
//让子类的原型变成父类的一个实例
Teddy.prototype = new Dog();
//手动给原型对象添加一个构造器,因为原型对象里面都是有构造器的,指向和自己匹配的构造函数
Teddy.prototype.constructor = Teddy;
var d1 = new Dog('旺财',3);
d1.run();
var t1 = new Teddy('小黑',2);
t1.run(10);
console.log(t1);
</script>
-
组合继承
- 原型继承方法,借用构造函数继承属性一起使用
-
方法重写和方法重载(多态的表现形式)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script type="text/javascript">
//父类
function Dog(name,age){
this.name = name;
this.age = age;
}
//子类
function Teddy(name,age){
// this.name = name;
// this.age = age;
//借助父类的构造函数实现属性的继承
Dog.call(this,name,age);
}
Dog.prototype.run = function(){
console.log('跑的很快~');
}
//为了让子类去继承父类原型当中的方法
//我们需要用到原型继承,原型继承主要就是为了让子类继承方法使用的
//让子类的原型变成父类的一个实例
//手动给原型对象添加一个构造器,因为原型对象里面都是有构造器的,指向和自己匹配的构造函数
Teddy.prototype = new Dog();
Teddy.prototype.constructor = Teddy;
//方法重写和方法重载 是多态的两个表现形式
//方法重写;和父类同名方法功能不同,被称作方法重写
Teddy.prototype.run = function(flag){
//方法重载
if(typeof flag === 'number'){
console.log('跑的很慢~');
}else{
console.log('跑的很快~');
}
}
var d1 = new Dog('旺财',3);
d1.run();
var t1 = new Teddy('小黑',2);
t1.run(10);
console.log(t1);
</script>
</body>
</html>
七、web Workers
-
H5规范提供了js分线程的实现, 取名为: Web Workers
-
相关API
-
Worker: 构造函数, 加载分线程执行的js文件
-
Worker.prototype.onmessage: 用于接收另一个线程的回调函数
-
Worker.prototype.postMessage: 向另一个线程发送消息
-
每个线程可以向不同线程发送消息 也可以接收不同线程传来的消息
-
主线程操作
-
发送消息: worker.postMessage(消息可以是任何数据)
-
接受消息: worker.onmessage = function(e){
console.log(e.data)//接收到的消息或者数据在事件对象的data属性当中
}
-
-
子线程操作
-
发送消息: worker.postMessage(消息可以是任何数据)
-
接受消息: worker.onmessage = function(e){
console.log(e.data)//接收到的消息或者数据在事件对象的data属性当中
}
-
-
-
不足
- worker内代码不能操作DOM
- 不能跨域加载JS
- 不是每个浏览器都支持这个新特性
-
计算得到fibonacci数列中第n个数的值
- 在主线程计算: 当位数较大时, 会阻塞主线程, 导致界面卡死
function fib(n){ if(n <= 2){ return 1; } return fib(n- 1) + fib(n - 2); }
- 在分线程计算: 不会阻塞主线程
js代码-myThread.js
function fib(n){ if(n <= 2){ return 1; } return fib(n- 1) + fib(n - 2); } self.onmessage = function(e){ e = e || window.event; // console.log(e.data);//就 可以拿到主线程给我 发过来的消息内容 var result = fib(e.data); self.postMessage(result);//分线程接受到主线程的消息,然后开始计算,把计算后的记过发消息再给主线程 }
html代码
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script type="text/javascript"> //第一步:实例化一个对象,初始化主线程和分线程关联对象 var worker = new Worker('./myThread.js'); worker.postMessage(100);//主线程发送消息给分线程,消息内容可以是任意类型 worker.onmessage = function(e){ e = e || window.event; //等待接受分线程计算完的结果 console.log(e.data); } console.log('大哥 js Ending~'); </script> </body> </html>
一、JSON
Json是一种轻量级的数据交换标准。
json是一种数据格式;现在我们大多数都是通过json数据格式进行前后端数据交互的,json本质上是一个字符串,简称json串
前端往后台传数据的时候,要传json格式的数据json串
在前端json串的格式原形就是对象或者对象的数组;所以我们要先把数据存储为对象或者对象的数组,然后转化为json串进行传递
JSON 是 JS 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。
eg:
var obj = {a: ‘Hello’, b: ‘World’}; //这是一个对象,注意键名也是可以使用引号包裹的
var json = ‘{“a”: “Hello”, “b”: “World”}’; //这是一个 JSON 字符串,本质是一个字符串
var a = '阿马';
var b = 100;
var c = '嘿嘿';
//第一步,把数据给封成一个对象
var obj = {
a: a,
b: b,
c: c
}
//第二步,把对象给变为json串,依赖JSON对象的方法
console.log(obj);
var result = JSON.stringify(obj); //把对象转化为json串
console.log(result);
//后端传递回来的数据,我们需要把这个json串转化为对象去操作
console.log(JSON.parse(result));
二、浅拷贝
浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用(地址)
Object.assign(target,...sources)
es6新增方法可以浅拷贝
var obj1 = {
name: "阿马",
age: 18,
eat: function () {
console.log("xxx")
}
}
//obj1和obj2指向同一个对象,不属于拷贝
var obj2 = obj1;
//拷贝obj1,得到一个新的对象obj3
var obj3 = {};
//for in遍历obj1,一个个的拿到里边的key和value
for (var key in obj1) {
obj3[key] = obj1[key];
}
console.log(obj3)
console.log(obj3 === obj1)
三、深拷贝
深拷贝拷贝多层,每一级别的数据都会拷贝
- 方案1
var obj1 = {
name: "阿马",
age: 18,
eat: function () {
console.log("xxx")
},
sister: {
name: "小宇",
sex: "姑娘",
age: 2
}
}
//深拷贝
function deepClone(obj) {
//判断类型 如果是基本类型 则直接返回 如果是对象类型,则开始拷贝
if (checkType(obj) === 'object') {
var newObj = {};
} else if (checkType(obj) === 'array') {
var newObj = [];
} else {
return obj;
}
//拷贝
for (var key in obj) {
//每次拷贝之前 把拷贝的递归一下,如果是基本值,则直接返回,否则再次拷贝
newObj[key] = deepClone(obj[key]);
}
return newObj;
}
var re = deepClone(obj1);
console.log(re);
console.log(re === obj1);
console.log(re.sister === obj1.sister);
//检测类型函数
function checkType(obj) {
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}
- 方案2
var obj1 = {
name: "阿马",
age: 18,
sister: {
name: "小宇",
sex: "姑娘",
age: 2
},
eat: function () {
}
}
console.log(JSON.stringify(obj1))
//深拷贝方法2,但是不能拷贝方法
var re = JSON.parse(JSON.stringify(obj1))
console.log(re);
console.log(re === obj1);
console.log(re.sister === obj1.sister);
下一章:jQuery学习