基本数据类型——undefined出现的情况:
//1、一个变量声明了,但是没有赋值,值默认为undefined
var b;
console.log(b);//b就是一个undefined类型的值
//2、一个变量声明了,并且赋值了一个undefined的值
var c=undefined;
console.log(c);//c也是一个undefined类型的值
//3、一个对象中,获取某个不存在的属性,值也是undefined
var d={};
console.log(d.age);//由于d对象没有age属性,值:undefined
对象的基本使用
//1、创建对象
var person={
gender:"男",
height:185,
toShanghai:function(){
console.log('做着高端大气上档次的和谐号动车组');
}
};
//2、获取属性
console.log(person.gender); //"男"
console.log(person["height"]); //185
//3、设置属性
person.address="上海市闸北区";
person.height=190;
//4、删除属性
delete person.gender;
delete person.a; //这段代码没有意义,也不会报错
//delete关键字只能删除对象中的属性,不能删除变量
// var a=100;
// delete a;//错误的理解
//清空对象
person={}; //person对象不再具有任何属性
person=null;
//表示将person变量的值赋为null,从此以后person不再是一个对象了
构造函数、实例
//1、
function _fn(){
return 5;
}
var s=new _fn();
//s是_fn构造函数的实例
//2、
function _fn3(){}
var q1=new _fn3();
//q1是_fn3构造函数的实例
//3、
function fn3(){
return [1,3,5];
}
var qw=new fn3();//qw=[1,3,5]
console.log(qw);
//qw不是fn3构造函数的实例
//如何判断一个数据是否是复杂数据类型?
//使用排除法:
//a、看它的值是不是:数字、字符串、布尔值、null、undefined,
//b、如果不是以上5种值,那就是复杂数据类型
// 举例:
// [1,3,5]
// /abc/
// function(){}
// new Object();
//为什么要理解构造函数的返回值?
//String是一个内置函数
//a、String()
//b、new String()
//结论:一个函数通过new调用,或者不通过new调用,很多时候会有截然不同的返回值
//我如何分辨出一个对象到底是不是某个构造函数的实例?
//a、var isTrue=xxx instanceof Person
function Person(){
}
var p1=new Person();
console.log(p1 instanceof Person);//true,就是Person的实例
function Student(){
return 100;
}
var s1=new Student();
console.log(s1 instanceof Student);//true,就是Student的实例
function Programmer(){
return [1,3,5]
}
var pro=new Programmer();//pro并不是Programmer的实例
console.log(pro instanceof Programmer);//false
console.log("是数组的实例吗?",pro instanceof Array);//true
//小技巧:如何通过肉眼识别xxx对象是哪个构造函数的实例?
//xxx.__proto__属性,也是对象,该对象中一般都会有一个constructor属性,这个指向PPP函数,那么就可以认为:xxx是PPP构造函数的实例
//typeof运算符,只能判断:数字、字符串、布尔值、undefined、函数
//切记:千万不能使用typeof运算符来判断对象的构造函数
//typeof null === "object"
//typeof {} === "object"
//typeof [] === "object"
//typeof function(){} === "function"
//typeof /abc/ === "object"
原型对象
function Person(name,age){
this.name=name;
this.age=age;
this.say=function(){}
}
var p1=new Person();
var p2=new Person();
//p1对象和p2对象的say方法是否是同一个方法:false
console.log(p1.say===p2.say);
//由于say方法可能功能相似,但是不是同一个方法(没有指向同一块内存,会造成内存浪费)
//解决方案:把say方法写在他们共同的(父对象)中
//其实他们共同的父对象,就可以通过:Person.prototype来获取
//-->只要把say方法写在Person.prototype中,那么say方法就是同一个方法
Person.prototype.run=function(){
console.log('时速500KM');
}
//此时p1和p2都可以访问到run方法
p1.run();
p2.run();
//验证p1.run和p2.run是否是同一个方法?
console.log(p1.run == p2.run); //指向同一个方法,这种方法避免了内存的浪费
console.log(p1.run == Person.prototype.run);
//true
var p3=new Person();
console.log(p3.run == p1.run); //true
console.log(p3.run === p1.run);//true
//结论:只要往某个构造函数的prototype对象中添加某个属性、方法,那么这样的属性、方法都可以被所有的构造函数的实例所共享
//==>这里的【构造函数的prototype对象】称之为原型对象
// Person.prototype是 p1 p2 p3 的原型对象
// Person.prototype是Person构造函数的【实例】的原型对象
//猜猜看?
// Person的原型对象是谁呢?
// -->首先要知道Person的构造函数:-->Function
// -->所以Person的原型对象是:Function.prototype
// p1的原型对象是谁呢?
// -->首先要知道p1是谁创建的? -->Person
// -->所以p1的原型对象时: Person.prototype
继承方式1:原型链继承
function Person(){}
Person.prototype={
constructor:Person,
say:function(){
console.log("你好");
},
run:function(){
console.log("正在进行百米冲刺");
}
}
var p1=new Person();
var p2=new Person();
console.log(p1.say === p2.say); //true
// 注意点:
// a、一般情况下,应该先改变原型对象,再创建对象
// b、一般情况下,对于新原型,会添加一个constructor属性,从而不破坏原有的原型对象的结构
继承方式2:拷贝继承
场景:有时候想使用某个对象中的属性,但是又不能直接修改它,于是就可以创建一个该对象的拷贝
//1、已经拥有了o3对象
var o3={gender:"男",grade:"初三",group:"第五组",name:"张三"};
//2、创建一个o3对象的拷贝(克隆):for...in循环
var o4={};
//a、取出o3对象中的每一个属性
for (var key in o3) {
//key就是o3对象中的每一个属性
//b、获取到对应的属性值
var value = o3[key];
//c、把属性值放到o4中
o4[key] = value;
}
//3、修改克隆对象,把该对象的name属性改为"李四"
o4.name="李四"
console.log(o4); //最终的目标对象的结果
//后续如果修改了o4对象中的相关属性,就不会影响到o3
扩展:es6中有了 <对象扩展运算符> 仿佛就是专门为了拷贝继承而生:
- 优点:简单的令人发指
var source={name:"李白",age:15}
//让target是一个新对象,同时拥有了name、age属性
var target={ ...source }
var target2={ ...source,age:18 }
继承方式3:原型式继承
- a、创建一个纯洁的对象:对象什么属性都没有
- b、创建一个继承自某个父对象的子对象
var parent={ age:18,gender:"男"};
var student=Object.create(parent);
//student.__proto__===parent
继承方式4:借用构造函数继承
+ 场景:适用于2种构造函数之间逻辑有相似的情况
+ 原理:函数的call、apply调用方式
+ 局限性:Animal(父类构造函数)的代码必须完全适用于Person(子类构造函数)
function Animal(name,age){
this.name=name;
this.age=age;
}
function Person(name,age,address){
Animal.call(this,name,age);
// 或者Animal.apply(this, [name,age])
this.address=address;
}
继承方式5:寄生继承、寄生组合继承
+ 概念:JS里面的对象可能会有父对象,父对象还会有父对象,。。。。。祖先
+ 根本:继承
- 属性:对象中几乎都会有一个__proto__属性,指向他的父对象
-意义:可以实现让该对象访问到父对象中相关属性
+ 根对象:Object.prototype
- var arr=[1,3,5]
- arr.__proto__:Array.prototype
- arr.__proto__.__proto__找到了根对象
function Animal(){}
var cat=new Animal();
//cat.__proto__:Animal.prototype
//cat.__proto__.__proto__:根对象
ES6中的面向对象写法
class Person{
// 构造函数
constructor (name,age) {
this.name = name
this.age = age
}
// 静态方法
static maxAge (name) {
if (name >= 120) {
console.log('年龄不能超过120')
}
}
// 实例方法
travel () {
console.log(`${this.name}去了浪漫的土耳其`)
}
}
// 继承
class Student extends Person{
constructor (name,age) {
super(name,age)
}
}
let s1 = new Student('小明',15)
Person.maxAge(125)
s1.travel()