JS高级
预解析内容
- 代码段是指一个script标签就是一个代码段。JS代码在执行时,是一个代码段一个代码段执行。
- 代码段是相互独立的上面的代码段报错了,不会影响下面的代码段
- 可以在下面代码段中使用上面代码段中的数据!
<script>
var a = '我是第一个代码段中的a';
console.log(b);//b is not defined
//在上面的代码段里不能使用下面定义的数据
</script>
<script>
var b = '我是第二个代码段中的b';
console.log(a);//打印'我是第一个代码段中的a'
//在下面的代码段里能使用上面定义的数据
</script>
什么是预解析
- 浏览器在执行JS代码的时候会分成两个部分操作:预解析和逐行执行代码
- 也就是浏览器不会直接执行代码,而是加工处理后再执行
- 这个加工处理的过程就是预解析
预解析会做什么?
- 预解析期间会提升变量,在代码段中加var 的变量 和 function声明的函数会提升到代码段的最前面,注意:变量提升只提升声明,函数提升的是函数整体!
- 除了代码段中的全局数据会提升外,函数体内部的局部变量也会提升,注意:局部变量在函数内部提升只提升声明。
- 结论:预解析会变量提升,变量只提升声明,函数提升整体!
- 注意:提升的同名变量名和函数名,函数会覆盖变量,函数在JS中是一等公民优先级高
- 注意:当函数名重复声明并赋值的话 只会提升第一个
预解析练习题
- 题目1
<script>
//预解析提升:
//var a; fn(){};
console.log(a);//undefined
var a = 110; //给a 赋值为110
console.log(a);//110
fn(); //调用fn函数 打印 我是一个fn函数
function fn(){
console.log('我是一个fn函数');
}
</script>
- 题目2
<script>
//预解析提升:
//var g;
g(); //g提升后值为 undefined 所以结果为g is not a function
var g = function(){ //g 是一个变量 不是函数
console.log('g函数执行了..');
}
</script>
- 题目3
<script>
//预解析提升:
//var i;
console.log(i);//undefined
for(var i = 0; i < 10; i++){}//循环给i赋值
console.log(i);//i = 10
</script>
- 题目4
<script>
//预解析提升:
//var a; fn(){};
//var b; var a; var c;
var a = 666; //给全局变量a赋值 由undefined变为666
fn(); //调用函数
function fn() {
var b = 777; //给局部变量b赋值 由undefined变为777
console.log(a);//打印 und
console.log(b);//打印 777
console.log(c);//打印 und
var a = 888;
var c = 999;
}
</script>
- 题目5
<script>
//预解析提升:
//var value; function value(){}
console.log(value); //value由und 变为value函数体
var value = 123; //value由函数体变为123
function value() {
console.log('我是value函数...');
}
console.log(value); //打印123
//结论:提升的同名变量名和函数名,函数会覆盖变量,函数在JS中是一等公民优先级高
</script>
- 题目6
<script>
//预解析提升:
console.log(a);//a 没有加var 不提升 报错 a is not defined
a = 666;
console.log(a);//上面代码报错下面代码不执行
</script>
- 题目7
<script>
//预解析提升:
//fn(){};
function fn(){
console.log(a);//找a 没有a a is not defined
a = 666; //代码是从上到下执行的 没有运行到这一行 如果运行到这一行的话 会在VO中存一个 a = 666
console.log(a);//不执行
}
fn();
console.log(a); //不执行
</script>
- 题目8
<script>
//预解析提升:
//fn(){}
function fn(){
a = 110;
}
console.log(a); //函数没有执行 找不到a a is not defined
</script>
- 题目9
<script>
//预解析提升:
//gn(){}
//var k;
gn();
function gn(){
var k = 123;
console.log(k); //123
}
console.log(k); //k is not defined
</script>
执行上下文
- 全局执行上下文和局部执行上下文
- 当全局代码执行时,就会产生一个全局的执行上下文,EC(G);
- 当函数代码执行时,就会产生一个局部的执行上下文,EC(Fn)。只要调用一个函数,就会产生一个局部执行上下文。调用100个函数,就会产生100个执行上下文。
- 在JS代码在执行时,肯定会先执行全局代码,就会产生ECG,这个ECG就要入栈。当我们调用一个函数,就会产生一个EC,这个EC就要入栈,当函数调用完毕后,EC就要出栈,又进入ECG,当全局代码执行完毕后,ECG也要出栈。
<script>
/*
在JS代码在执行的时候会先执行全局代码,执行全局代码就会产生一个ECG,这个ECG就要入栈
当我们调用函数的时候就会产生一个EC这个EC就要入栈,当函数调用完毕,就会出栈,又进入ECG
当全局代码执行完毕后,ECG也要出栈。
*/
/*
ECG在执行代码之前会预解析然后把全局定义的变量函数放入VO(GO),GO中包括了内置的方法,window,以及自己声明的全局数据
作用域链:ECG的作用域链就是VO本身
ECF在执行代码解析前会创建一个AO对象AO中包含形参,arguments,函数定义和指向函数对象,定义的变量
作用域链:ECF作用域链由AO和父级VO组成,查找数据会一层一层查找
*/
/*
在函数内部不加var定义的变量为全局变量,全局变量只会保存在VO中
*/
</script>
<!-- <script>
//原题
var n = 110;
console.log(n);
console.log(window.n);
m = 220;
console.log(m);
console.log(x);
</script>
<script>
//分析
//var n;
//n = 110;
var n = 110;
console.log(n);//110
console.log(window.n);//全局代码会放在GO中 GO就是window 所以n=110
m = 220;
console.log(m);//不加var的全局代码不会提升。m=220
console.log(x);//x没有声明,x is not defined
</script> -->
<!-- <script>
//原题
var a = 1;
var b = 'hello';
function fn() {
console.log('fn...');
}
var arr = ['a', 'b', 'c'];
var obj = { name: 'wc', age: 100 }
</script>
<script>
//分析
//var a; var b; fn(){}; arr; obj;
var a = 1;
var b = 'hello';
function fn() {
console.log('fn...');
}
var arr = ['a', 'b', 'c'];
var obj = { name: 'wc', age: 100 }
</script> -->
<!-- <script>
//原题
function fn(a) {
console.log(a);
}
fn(110);
console.log(a);
</script> -->
<!-- <script>
//分析
//fn(){};
//var a;
function fn(a) { //函数形参(局部变量)变为110
console.log(a); //110
}
fn(110);
console.log(a);
</script> -->
<!-- <script>
//原题
var arr = [11, 22];
function fn(arr) {
arr[0] = 100;
arr = [666];
arr[0] = 0;
console.log(arr);
}
fn(arr);
console.log(arr);
</script> -->
<!-- <script>
//分析
//arr; fn(){};
//arr;
var arr = [11, 22];//栈地址 0x666
function fn(arr) { //给形参arr 赋值为 0x666
arr[0] = 100; //0x666指向的堆 数据变为[100,22];
arr = [666]; //把新堆[666]的地址0x888赋值给 形参arr 此时形参中的arr指向了 0x888
arr[0] = 0; //修改ox888中的数据 ox888指向的堆的数据由[666]变为[0]
console.log(arr); // 打印ox888堆中的数据 [0]
}
fn(arr);//形参arr 赋值为 0x666
console.log(arr); //打印全局执行上下文中的arr 它指向ox666 数据为[100,22]
</script> -->
<!-- <script>
//原题
var a = 1;
var b = 1;
function gn() {
console.log(a, b);
var a = b = 2;
console.log(a, b);
}
gn();
console.log(a, b);
</script> -->
<!-- <script>
//分析
//var a; var b; gn(){};
//var a;
var a = 1;
var b = 1;
function gn() {
console.log(a,b); //a 为und b 没有提升在AO找不到 就去父级VO中找 父级VO中有b = 1 所以und 1
var a = b = 2;
console.log(a,b); //a 赋值为2 b赋值为2 所以父级中的b 由1 变成了2
}
gn();
console.log(a, b); //打印ECG中的a,b结果为:1,2
</script> -->
<!-- <script>
//原题
var a = 1;
var obj = { uname:'wangcai'}
function fn(){
var a2 = a;
obj2 = obj;
a2 = a;
obj.uname = 'xiaoqiang';
console.log(a2);
console.log(obj2);
}
fn();
console.log(a);
console.log(obj);
</script> -->
<!-- <script>
//分析
//var a; obj; fn(){}; obj2
//var a2;
var a = 1;
var obj = { uname: 'wangcai' }
function fn() {
var a2 = a; //找a a在VO中值为1 赋值给a2 a2为1
obj2 = obj; //把obj的地址0x666复制给了 obj2
a2 = a; //又把 VO中的1 赋值给了 a2
obj.uname = 'xiaoqiang'; //0x666 中的wangcai变为了xiaoqiang
console.log(a2); //打印a2 为1
console.log(obj2); //打印obj2 为ox666中的xiaoqiang
}
fn();
console.log(a); //打印VO中的a 为1
console.log(obj);//打印obj 为xiaoqiang
</script> -->
<!-- <script>
//原题
var a = [1,2];
var b = a; //b 和 a 指向同一个堆
console.log(a,b); //[1,2] [1,2]
b[0] = 110; //[110,2]
console.log(a,b);//[110,2] [110,2]
</script> -->
<!-- <script>
//原题
var a = [1,2];
var b = [1,2];
console.log(a == b);//false == 比较的是地址 不同的堆 地址是不可能一样的
console.log(a === b);//false
</script> -->
<!-- <script>
//原题
var a = [1,2];
var b = a;
console.log(a == b);//a 和 b指向同一个堆 所以为true
console.log(a === b);//a 和 b指向同一个堆 所以为true
</script> -->
<!-- <script>
//原题
console.log(a,b);//a变量提升本身应该为und 但是b没提升 b为 b is not defined 所以直接报错
var a = b = 3;
</script> -->
<!-- <script>
//原题
var a = {m:666};
var b = a;//把a的地址赋值给b
b = {m:888};//把新地址赋值给b b的地址变了
console.log(a);//a的值还是m:666没变化
</script> -->
<!-- <script>
var a = {n:12};
var b = a;
b.n = 13;
console.log(a.n);//13
</script> -->
<!-- <script>
console.log(a); //a is not defined
a = 110;
</script> -->
<!-- <script>
var m = 1;
n = 2;
console.log(window.m);//1
console.log(window.n);//2
</script> -->
<!-- <script>
function fn(){
var a = 111;
}
fn();
console.log(window.a);//undefined
</script> -->
<!-- <script>
var a = -1;
if(++a){ //++a的值为新值 为0 0 为false
console.log('666');// 不执行
}else{
console.log('888');//打印888
}
</script> -->
<!-- <script>
//var a; var b;
console.log(a,b);//und und
if(true){
var a = 1;
}else{
var b = 2;
}
console.log(a,b);//1 und
</script> -->
<!-- <script>
var obj = {
name:'wc',
age:18
}
//in 为运算符 判断一个属性是否属于某个对象
console.log('name' in obj);//true
console.log('score' in obj);//false
</script> -->
<!-- <script>
//var a;
var a;
console.log(a);//und
if('a' in window){
a = 110;
}
console.log(a);//110
</script> -->
<!-- <script>
//var a;
console.log(a);//und
if('a' in window){
var a = 110;
}
console.log(a);//110
</script> -->
<!-- <script>
//var a; fn(){};
//var a;
var a = 100;
function fn(){ //函数内部有a 并且加var了 所以提升 先提升再执行
console.log(a); //所以还没有执行到return 打印undefined
return;
var a = 100;
}
fn();
</script> -->
<!-- <script>
//提升
//var n; foo(){};
//foo中把全局变量中的n的值改成了200
var n = 100; //这两个n是同一个n
function foo(){
n = 200; //这两个n是同一个n
}
foo();//调用完函数后把全局变量中的n的值改成了200
console.log(n);//打印全局变量n = 200
</script> -->
<!-- <script>
//fn(){};
//var a;
function fn(){
var a = b = 100;//var a = 100; b = 100; b在AO中没有定义
} //所以到VO中找 但是VO中也没有 所以报错
fn();
console.log(a);//100
console.log(b);//b is not defined
</script> -->
<script>
//var n; fn(){}; gn(){};
//
var n = 100;//全局变量n = 100
function fn(){
console.log(n);//打印n 自己里面没有n 按照作用域链到父级找
//找到GO中全局变量n = 100
}
function gn(){
var n = 200;
console.log(n);//200
fn()//fn()在全局代码段中调用 父级是GO
}
gn();//200 100
console.log(n);//100
</script>
深入变量
加var变量和不加var变量的区别:
- 加var的全局变量会在预编译期间提升到代码段的最前面,不加var的变量不会提升。
- 加var的变量有可能是全局变量,也有可能是局部变量,不加var的变量只可能是全局变量。
- 加var的局部变量不会作用到window的属性。
let声名的变量:
- let声明的变量不会提升
- let声明的变量不会作用到window的属性
- let和{}配合产生局部作用域 局部作用域外部访问不到{}里面的数据
const声明的变量:
- const主要声明常量,const声明必须初始化赋值
- const声明的变量不可以改变
- const声明的变量不会提升
- const和{}配合会形成块级作用域,块里面的数据外部访问不到
- const声明的常量也不会挂载到GO上
闭包
什么是闭包
- 闭包就是一个不能被销毁的栈空间(内存)局部EC
- 一个普通函数可以访问外层作用域的自由变量,那么这个函数就是一个闭包
- 闭包的作用:
- 延长闭包中数据的生命周期,所对应的内存会一直存在不会被释放掉,造成内存泄漏
- 外面想去访问里面的数据,是访问不了的
函数是一等公民
- 在JavaScript中,函数是非常重要的,并且是一等公民 高阶函数就是函数可以作为另一个函数的参数,也可以作为另外一个函数的返回值来使用
IIFE
IIFE就是立即函数调用表达式的意思。
<script>
//IIFE 就是立即调用函数表达式的意思
//函数后加小括号 然后整体用小括号包起来
//01(function say(){ console.log('hello');}())
//02+function say(){ console.log('hello');}()
//03-function say(){console.log('hello');}()
//04!-function say(){console.log('hello');}()
//05(function say(){console.log('hello');})()
//如果IIFE前面是return +-!()可以不写
//也就是IIFE是函数内部的return后的函数的时候 +-!()可不写
</script>
高阶函数:
- 一个函数的参数是函数 或 他的返回值是函数,那么这个函数就是高阶函数。
- 自己手写的高阶函数 函数的参数是一个函数
function fn(a){
a();
};
function gn(){
console.log(666);
}
fn(gn);
- 数组中的高阶函数
- filter 过滤函数返回值是过滤得到的新数组
- map 映射函数
- forEach快速遍历
- find查找函数 返回的是找的到元素 找不到返回und
- findIndex 查找函数 返回的是 数组下标 找不到返回-1
<script>
let nums = [2, 23, 77, 45, 3, 8, 10];
// nums.filter() 表示调用函数
// nums.filter(function(){}) 把一个匿名函数作为函数的实参 传给filter
//filter是JS中内置好的高阶函数
//filter的返回值是一个数组
//匿名函数作为filter高阶函数的实参 这个匿名函数也有自己的形参item item 表示数组的每一项
//就相当于 匿名函数的形参item 在匿名函数里的arguments 包含了所有的实参
let newArray = nums.filter(function (item) {
// return true; 表示把item中的每一项都放到新数组中
return item % 2 == 0;
})
console.log(newArray);
</script>
<script>
//map 映射 对数组中的每一项进行加工
let nums1 = [2, 23, 77, 45, 3, 8, 10];
let newArray1 = nums1.map(function(item){
return itme * 10;
})
console.log(newArray1);
</script>
<script>
//forEach
let nums2 = [2, 23, 77, 45, 3, 8, 10];
let newArray2 = nums2.forEach(function(item){
console.log(item);
})
</script>
<script>
//find
let nums3 = [2, 23, 77, 45, 3, 8, 10];
let itemm = nums3.find(function(item){
return item === 8;
})
console.log(itemm);//返回的是查找的元素 找不到返回的是 und
</script>
<script>
//findIndex
let nums4 = [2, 23, 77, 45, 3, 8, 10];
let itemm = nums4.findIndex(function(item){
return item === 3;
})
console.log(itemm);//返回的是 数组下标
</script>
this指向与this相关
默认绑定
- 默认绑定就是独立函数调用,可以理解为用window调用了这个函数只不过没有加.所以独立函数调用中this为window
隐式绑定
- 独立函数调用,把对象内的方法地址传给外部变量,通过外部变量加()调用对象里面的方法,此时方法里的this表示的是window
- 通过对象.方法名+()调用对象内部的方法,方法中出现的this就看.前面的是谁,this就代表了谁。
显示绑定
- 函数也是对象 上面有几个默认的方法 通过call apply bind 可以改变this指向
使用方法:想要改变this指向的函数名.call(想要指向的东西);
使用方法:想要改变this指向的函数名.apply(想要指向的东西);
使用方法:想要改变this指向的函数名.bind(想要指向的东西);
注意:
call 和 apply 不仅可以显示绑定this 而且还会让函数执行
call 会将传入想要指向的东西 包装成一个新的对象返回
bind 显示绑定this 不会让函数执行 返回一个绑定this 之后新函数
new绑定
- 使用new创建一个对象
第一步这个new会在类中生成一个空的对象(堆)
第二步这个new中会生成一个this 这个this指向了这个堆
第三步使用类生成的这个对象也会指向这个堆
规则优先级
- 默认规则的优先级最低
- 显示绑定优先级高于隐式绑定
- new绑定优先级高于隐式绑定
- new绑定优先级高于隐式绑定
- new绑定优先级高于bind
- new绑定和call、apply是不允许同时使用
- new绑定可以和bind一起使用,new绑定优先级更高
忽略显示绑定
- 如果在显示绑定中,我们传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则
箭头函数中this指向
- 箭头函数中的this指向外面一层。
面向对象
创建对象的几种方式
<script>
创建对象的几种方式:
字面量法:
let obj = {};
new方法:
let obj = new Object(); new一个Object类 生成一个obj对象 //Object叫类也叫构造器
let str = new String(); new一个String类 生成一个str对象 //String叫类也叫构造器
工厂模式:
function Person(name,age){
let obj={};
this.name = name;
this.age = age;
return obj;
};
let cc = Person('陈城',21);
let gh = Person('高涵',22);
打印出它的类时,都是Object 每个对象都是Object的实例
这种方式 生成的对象 对应的全部都是 Object类 不能很真实的反应对象所对应的类
构造函数法:
创建一个手机类
function Phone(name,price){
this.name = name;
this.price = price;
}
let mi = new Phone('小米',1999);
new做了什么:
在构造器的内部创建了一个新的对象
让构造器中的this指向这个对象
执行构造器中的代码
如果构造器没有返回对象,那么返回这个被创建的新的对象
这个对象内部的prototype属性会被赋值为该构造函数的prototype属性
构造器+原型创建对象
把共有的属性放到原型上
function Player(name){
this.name = name;
}
Player.prototype.game() = function(){
console.log(this.name + '开始比赛');
}
let qiaodan = new Player('乔丹');
qiandan.game();
位于类上的属性叫做:静态属性
判断 一个对象是否属于 一个类 对象.instanceof(类)
str.instaceof(Number);//false
</script>
私有属性和公有属性
<script>
/*
一切都是对象
对象是属性的无序集合
原型链:
查找对象上的属性,如果对象本身的私有属性中没有,那么会沿着__proto__到创建对象的构造器的原型对象中找
每一个对象都有一个__proto__那么构造器的原型对象也有一个__proto__ 在创建对象的构造器的原型对象中
还是找不到的话 也会沿着__proto__去Object 的原型对象中找 找不到的话 继续沿着__proto__找 找到null
还找不到就报错,这个沿着__proto__查找属性的链 就叫原型链。
每一个函数(构造器中都有一个prototype属性) 这个prototype指向这个构造器的原型对象
每一个对象都有一个__proto__的属性 这个属性指向 创建它的构造器的原型对象
原型对象中不仅有__proto__而且还有一个constructor指向创建原型对象的构造器
构造器的prototype指向和这个构造器生成的对象中的__proto__指向的是同一个原型对象
构造器的prototype指向创建的原型对象
所有函数的默认原型都是Object的实例
通过__proto__可以向上查找属性
*/
</script>
<script>
let arr2 = new Array('a','b','c');
console.log(arr2);
console.log(arr2.__proto__);//Array 原型对象
//打印创建出arr2这个对象的原型对象 也就是打印Array的原型对象
console.log(arr2.__proto__.__proto__);//Object 原型对象
//每一个对象都有一个__proto__ 那么 Array的原型对象的__proto__也指向创建Array的原型对象的原型对象 这个对象为Object
console.log(Array.prototype == arr2.__proto__);
console.log(arr2.__proto__.constructor == Array);//true
//每一个原型对象上不仅有一个__proto__ 还有一个constructor 这个constructor 指向这个构造器
console.log(arr2.__proto__.constructor);//Array 原型对象
</script>
精细化设置对象中的属性
<script>
/*
精细化设置对象中的属性
数据属性描述符 configurable enumerable writable value
Object.defineProperty(目标对象,作用的属性,{
value:设置的属性值,
configurable:是否可以删除,//默认都是true ture表示可以删除
enumerable:是否可以枚举,//默认都是true ture表示可以枚举
writable:是否可以重新赋值//默认都是true ture表示可以修改
})
存取属性描述符 get set
get:当获取属性的时候会执行的函数 默认为undefined
set:当设置属性的时候会执行的函数 默认为undefined
Object.defineProperty(目标对象,作用的属性,{
configurable:false,
enumerable:false,
get:function(){
console.log('get...');
return this._address;
}
})
*/
</script>
<script>
/*
get作用:
获取器:当调用添加精细化添加的属性 那么就会自动调用defineProperty中的get 返回对象中的属性
set作用:
设置器:当给精细化属性重新赋值的时候,就会自动调用set函数 把修改的值 赋值给对象中的属性
*/
let obj = {
name:'sc',
age:22,
_address:"商丘"
}
Object.defineProperty(obj,'address',{
configurable:false,
enumerable:false,
get:function(){ //获取器 当调用address 就执行里面的函数
// console.log('get..');
return this._address;//可以是对象的任意属性
},
set:function(value){ //设置器 当修改address 就执行里面的函数 把_address的值修改为 传入的address的值
// console.log('set...');
this._address = value;
}
})
console.log(obj.address);//商丘
obj.address = '博爱'; //修改对象中的属性值 把精细化属性值 "博爱" 赋值给对象中的属性值 this._address = value;
console.log(obj._address);//博爱
</script>
<script>
/*
给多个属性精细化设置
Object.definproperties(obj,{
作用的属性:{
},
作用的属性:{
}
})
*/
let obj = {
_age:18,
_address:'香港'
}
Object.defineProperties(obj,{
name:{
value:'修狗',
configurable:true,
enumerable:true,
writable:true
},
address:{
configurable:true,//可以删除
enumerable:true,//可以枚举
get:function(){
return this._address; //当调用address就会执行address里面的get方法返回_address
},
set:function(value){ //当修改address就会执行set方法 然后把对象中原本的属性对应的属性值改变
this._address = value;
}
}
})
console.log(obj.name);
console.log(obj.address);
obj.address = "台湾";
console.log(obj._address);
</script>
<script>
/*
get 和 set 在对象中的使用
*/
let obj = {
_name:'cc',
get name(){//获取器
return this._name;
},
set name(value){//设置器
this._name = value;
}
}
//当我们obj.name的时候就会返回 obj中的_name
console.log(obj.name);//'cc'
//当我们修改 obj.name的时候 就会自动调用对象里面的 set方法
obj.name = 'sc';
console.log(obj.name);//调用obj.name get方法执行 输出obj中的_name
</script>
继承
原型链继承
<script>
function Parent() {
this.hobby = ["eat", "run"]
}
Parent.prototype.getName = function () {
console.log(this.name);
}
function Child() { }
Child.prototype = new Parent(); //创建一个父类对象作为子类的原型对象
Child.prototype.constructor = Child;//新建的原型对象的constructor指向Child类
let c1 = new Child() //新建一个子类的对象
let c2 = new Child() //新建一个子类的对象
c1.hobby.pop(); // 删除子类原型对象中数组中的一个元素
console.log(c1.hobby); //["eat"]//因为子类对象全部都指向子类的原型对象
console.log(c2.hobby); //所以只要有子类对象对子类原型对象中的属性做修改
//其他子类对象再调用子类原型对象中的属性是时就会受到影响 (就是属性值发生了改变)
</script>
构造函数继承
<script>
/*
构造函数继承实质:
就是把父类中的this指向了子类
所以子类对象获得了父类中的属性
构造函数继承的不足:
父类中的属性和方法都想被子对象继承的话,只能把属性和方法写在构造函数中
如果是方法体的话,方法体一模一样,每创建一个子对象,都会有一个独立的堆空间
但是方法的代码是一模一样的,这就造成了内存空间的浪费
*/
//构造函数继承实质:
//就是让父类原本指向父类对象的this 指向子类的对象
//那么子类对象就可以通过父类添加属性
//利用call方法不仅可以修改父类中的this指向,而且可以直接给子类对象设置属性
//这样子类每new一次 子类对象就可以从父类那里获得到父类私有属性
function Parent(){
this.name = 'cc';
this.hobby= [1,3,5];
}
function Child(age){
this.age = age;
Parent.call(this);//更改父类this指向 使父类this指向子类对象
}
let c1 = new Child(20);
console.log(c1);//Child{age:20,name:'cc',hobby:[1,3,5]};
//得到的是父类的姓名和 自己的年龄
//需要自己设置姓名和爱好
</script>
组合继承
<script>
/*
组合继承:
第一:父类的this指向子类的this 子类对象可以获得父类的私有属性
第二:子类的原型对象 改变成了父类的对象, 子类的对象可以通过原型对象 获得 父类原型对象上的方法和属性。
总结:
这种方法不仅可以使得子类的对象继承了父类的私有属性,而且父类把方法添加到父类原型对象上
通过子类更改原型对象为父类的对象 可以获取父类原型对象上的属性和方法。这样方法在原型对象上 就不会造成内存空间浪费
*/
</script>
<script>
function Father(name,age){
this.name = name;
this.age = age;
}
Father.prototype.getName = function(){
console.log(this.name);
}
function Son(sex,name,age){
this.sex = sex;
Father.apply(this,Array.from(arguments).slice(1));
}
Son.prototype = new Father();
Son.prototype.constructor = Son;
let s1 = new Son('男','杜宜霖','22');
console.log(s1);
s1.getName();
</script>
寄生组合继承
<script>
function Parent(name, hobby) {
console.log("lalala");
this.name = name;
this.hobby = hobby;
}
Parent.prototype.eat = function () {
console.log("eat...");
}
function Child(id) {
Parent.apply(this, Array.from(arguments).slice(1))
this.id = id;
}
// Child.prototype = new Parent();
// 这样行,好吗?
// 不好,因为你让子的原型和父的原型指向了同一个对象
// 如果父修改了原型对象,子也要受到影响
Child.prototype = Parent.prototype;
Child.prototype.constructor = Child;
let c1 = new Child(1, "c1", ["haha"])
let c2 = new Child(2, "c2", ["xixi"])
c1.eat();
c2.eat();
</script>