1.复习原型
就像道格拉斯克罗克福德所说 - “每一个对象都与一个可以继承属性的原型对象相关联,从脆响文字创建的所有对象都链接到Object.prototype, 这是一个JavaScript标准对象。 ” “在这里,我们将探讨一些有趣的事情”
{prototype}(花括号的原型是隐藏的链接)是一个隐藏的链接,用户无法直接访问该对象,这个隐藏的链接也是一个对象,因此,有三种方法可以在JavaScript中创建对象,并且每种方法都可以创建原型链接。
目的 : 1、节省内存空间 2、实现数据共享
原形链 : 实例与原型的链条称作原型链
2.原型的指向是否可以改变
原型对象的constructor属性指向构造函数本身 ,让学生原型指向人的
Student.prototype = new Person("小王", 18);
console.log(Person.prototype.constructor === Person)//true
console.log(Student.prototype.constructor === Student)//false
结论:原型的指向可以发生改变。
3.原型链最终指向了哪里
就像道格拉斯克罗克福德所说 - “ 每一个对象都与一个可以继承属性的原型对象相关联。从对象文字创建的所有对象都链接到Object.prototype,这是一个JavaScript标准对象。“在这里,我们将探讨一些有趣的事情。
任何一个对象都有__proto__属性,实例化对象的__proto__属性指向的的构造函数的原型,因为任何一个对象都有__proto__属性,原型也是对象,所以原型也有__proto__属性,原型也有__proto__属性指向哪里呢?
猜测:原型的_proto__属性指向某个构造函数的原型
是Object构造函数的原型
console.log(Person.prototype);
console.log(Person.prototype.__proto__);
console.log(Person.prototype.__proto__ === Object.prototype);
Object.prototype也是对象,所以Object.prototype也有__proto__属性,指向哪儿?
console.log(Object.prototype.__proto__);//null
console.log(Object.prototype.__proto__ === null);//true
最终结论:原型链最终指向了null
4.什么是继承
(1)、现实生活中的继承
(2)程序中的继承
程序中的继承比较抽象,每一种语言实现的方式不同,但是思想都是相通的。Js中的继承说的一般的情况下是通过原型链的形式来实现的。
类–是一种代码的组织结构形式,是一种在软件中对真实世界中问题领域的建模方法。类有三个核心概念:封装、继承和多态。
面向对象编程强调的是数据和操作数据的行为本质上是互相关联的(当然不同的数据可能有不同的行为),因此好的设计就是数据以及和它相关的行为打包起来(也就是封装起来),这在正式的计算机科学中有时被称为 数据结构。
例如用来表示一个单词的一串字符通常被称为字符串。其中字符就是数据,而应用在这些数据上的行为(比如计算长度、添加数据、搜索),就被设计成了String类的方法。所有字符串都是String类的实例,可以说这个字符串是一个包裹,包含字符数据和可以应用在这个数据上的函数(行为或者方法)。
可以通过类来对数据结构进行分类,比如汽车类,它是交通工具类的一个特例,后者是更广泛的类
5.原型继承
可以通过类来对数据结构进行分类,比如汽车类,它是交通工具类的一个特例,后者是更广泛的类。
思考?为什么原型中的属性和方法都可以被实例化对象所继承呢?
是因为原型的constructor指向构造函数。
又因为原型链的指向可以发生变化。
所以子类去继承父类的所有的属性和方法,就让学生的原型指向父类即可。
Student.prototype.className = "一班";
Student.prototype.playLove = function () {
console.log(this.name + "爱搞对象!");
}
Student.prototype = new Person("小王", 18);
console.log(Student.prototype.constructor === Person);//true
为什么获取不到学生原型中的属性和方法?是因为设置完学生的原型中的属性和方法后,学生的原型指向发生改变了
如何解决?将学生原型的设置放在原型改变之后即可
Student.prototype = new Person("小王", 18);
console.log(Student.prototype.constructor === Person);//true
Student.prototype.className = "一班";
Student.prototype.playLove = function () {
console.log(this.name + "爱搞对象!");
}
通过原型继承,子类本身构造函数中的属性和方法以及子类原型中的属性和方法都可以获取到,并且父类构造函数中的属性和方法以及父类原型中的属性和方法也可以获取到。
6.构造函数继承
- 、call方法的作用
1、函数名.call()可以实现函数的调用
function fn1() {
console.log("我是普通函数");
}
// fn1();
fn1.call();
2、call()会改变this指向,如果没有参数this指向window
var name = "小丽";
var obj1 = {
name: "小王",
say: function () {
console.log(this.name + "爱说话!");
}
}
obj1.say();//小王爱说话!
obj1.say.call();//小丽爱说话!
- call()会改变this指向,如果有参数this指向该参数
var obj2 = {
name: "小刚",
say: function () {
console.log(this.name + "爱说话!");
}
}
obj2.say.call(obj1);//小王爱说话!
(2)、Call方法实现继承
4、call()会改变this指向,如果有参数this指向该参数中第一个参数,剩下的参数是个参数列表
我们可以通过call方法改变this指向的特性去实现继承, 构造函数中的this指向实例化对象,所以this指向实例学生。此时,通过call方法让人构造函数中的this指向学生即可。
call()继承不了父类原型中的属性和方法
// 通过call()方法可以改变this指向的特征去实现继承
// 父类(人类)
function Person(name, age) {
this.name = name;
this.age = age;
this.eat = function () {
console.log(this.name + "爱吃火锅!");
}
}
Person.prototype.sex = "女";
Person.prototype.sleep = function () {
console.log(this.name + "爱睡懒觉!");
}
// 子类(学生类)
function Student(score, name, age) {
this.score = score;
// this.name = name;
this.study = function () {
console.log(this.name + "爱学习!");
}
// 构造函数中的this指向实例化对象,所以this指向实例学生
Person.call(this, name, age);
// 4、call()会改变this指向,如果有参数this指向该参数中第一个参数,剩下的参数是个参数列表
}
Student.prototype.className = "二班";
Student.prototype.playPingPang = function () {
console.log(this.name + "爱打乒乓球!");
}
var stu = new Student(100, "小诸葛", 26);
// 1、学生构造函数的属性和方法
console.log(stu.score);//100
stu.study();
// 2、学生原型中的属性和方法
console.log(stu.className);
stu.playPingPang();
// 3、人构造函数的属性和方法
console.log(stu.name);
console.log(stu.age);
stu.eat();
// 4、人原型中的属性和方法
// call()继承不了父类原型中的属性和方法
console.log(stu.sex);
stu.sleep();//
7深拷贝与浅拷贝
- 、相关知识点
1.javascript变量包含两种不同数据类型的值:基本类型和引用类型。
基本类型值指的是简单的数据段,包括es6里面新增的一共是有6种,具体如下:number、string、boolean、null、undefined、symbol。
引用类型值指那些可能由多个值构成的对象,只有一种如下:object。
在将一个值赋给变量时,解析器必须确定这个值是基本类型值还是引用类型值。
2.javascript的变量的存储方式:栈(stack)和堆(heap)。
栈:自动分配内存空间,系统自动释放,里面存放的是基本类型的值和引用类型的地址
堆:动态分配的内存,大小不定,也不会自动释放。里面存放引用类型的值。
基本类型占用空间固定,保存在栈中(当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁了。因此,所有在方法中定义的变量都是放在栈内存中的;栈中存储的是基础变量以及一些对象的引用变量,基础变量的值是存储在栈中,而引用变量存储在栈中的是指向堆中的数组或者对象的地址,这就是为何修改引用类型总会影响到其他指向这个地址的引用变量。
基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。
引用类型的值是保存在内存中的对象。JavaScript 不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。 在操作对象时, 实际上是在操作对象的引用而不是实际的对象。
基本类型与引用类型最大的区别实际就是 传值与传址 的区别
值传递:基本类型采用的是值传递。
引用类型占用空间不固定,保存在堆中(当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(方法的参数传递时很常见),则这个对象依然不会被销毁,只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它
地址传递:引用类型则是地址传递,将存放在栈内存中的地址赋值给接收的变量。
- 、浅拷贝
定义:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。
基本数据类型Number(赋值操作)
var a=1;
var b=a;
b //1
b=2;
b //2
a //1
数组
var arr1 = [1,2,3];
var arr2 = arr1;
arr2 //[1,2,3]
arr2.push(4);
arr2 //[1,2,3,4]
arr1 //[1,2,3,4]
首先栈内存arr1会指向堆内存里的数组,栈内存的arr1保存的是数组的引用,也就相当于内存地址,arr2=arr1,会把arr1的引用赋给arr2,所以arr2也有了数组的引用,此时arr1和arr2指向的是同一个数组,因此一个数组的改变会影响另一个数组的值。
对象
var obj1={count:1,name:'grace',age:1};
var obj2 = obj1;
obj2 //{count:1,name:'grace',age:1}
obj2.count=2;
obj1 //{count:2,name:'grace',age:1}
obj2 //{count:2,name:'grace',age:1}
综上所述,如果是基本数据类型,直接进行赋值操作,这样就相当于在栈内存中重新开辟了一个新的空间把值传递过去;如果是引用类型的值传递,进行的就是浅拷贝,浅拷贝赋值的只是对象的引用,如上述obj2=obj1,实际上传递的只是obj1的内存地址,所以obj2和obj1指向的是同一个内存地址,所以这个内存地址中值的改变对obj1和obj2都有影响。
- 、深拷贝
定义:深拷贝不仅将原对象的各个属性逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深复制的方法递归复制到新对象上,所以对一个对象的修改并不会影响另一个对象。
数组(注意:该数组只是单纯的数组,如果数组里面嵌套对象,则下面的几种方法都不是深拷贝。)
法一:for循环
var arr1 = [1,2,3];
var arr2 = copyArr(arr1);
function copyArr(arr){
var res=[];
for(var i=0,length=arr.length;i<length;i++)
{
res.push(arr[i]);
}
return res; }
法二: slice
var arr1 = [1,2,3];
var arr2 = arr1.slice(0);
法三: concat
var arr1 = [1,2,3];
var arr2 = arr1.concat();
对象
法一:for循环
let obj1={count:1,name:'grace',age:1};
let obj2 = copyObj(obj){
let res = {};
for(let key in obj){
res[key]=obj[key]; }
return res; }
8.拷贝继承
定义:深拷贝不仅将原对象的各个属性逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深复制的方法递归复制到新对象上,所以对一个对象的修改并不会影响另一个对象。
利用对象的深拷贝方式即可,可以继承父类中构造函数和原型中的所有的属性和方法。
for (x in p1) {
// 对象的属性
// console.log(x);
// 对象的属性值
console.log(p1[x]);
// 只需要拷贝属性值即可
stu1[x] = p1[x];
}
9.函数内this的指向
函数的调用方式决定了 this 指向的不同:
调用方式 | 非严格模式 | 备注 |
普通函数调用 | window | 严格模式下是 undefined |
构造函数调用 | 实例对象 | 原型方法中 this 也是实例对象 |
对象方法调用 | 该方法所属对象 | 紧挨着的对象 |
事件绑定方法 | 绑定事件对象 | |
定时器函数 | window |
这就是对函数内部 this 指向的基本整理,写代码写多了自然而然就熟悉了。
10.函数也是对象
函数是一种特殊的引用类型
// 了解
function fn1() {
}
console.log(typeof fn1);
// 本质是
var fn2 = new Function("num1", "return num1");
console.log(fn2(666));
console.log(fn2.__proto__ == Function.prototype);//true
// 函数也是对象