一.对象类型数据结构的基本结构和操作
对象是一组属性的无序集合,任何一个对象都是由0到多组键值对(属性名:属性值)组成的,并且属性名不能重复。如下:
1.1对象字面量创建对象
person = {
name: "蔡徐坤",
age: 23,
weight: "60kg",
height: "184cm",
1:'100分'
};
//1.获取属性,通过对象的属性名获取,属性名是数字或者字符串;
console.log(person.name);//蔡徐坤
console.log(person["age"]);//23
//2.若果属性是数字,只能通过person[number]获取;
console.log(person[1]);
//3.若果对象属性不存在,不会报错,是undefined;
console.log(person.hobby);//undefined
//4.修改属性值,操作已有的属性名
person.name='蔡小葵'
//5.新增属性值,操作没有的属性名
person.hobby="singing"
复制代码
1.2 构造函数创建对象
1.2.1 系统构造函数new Object()
obj = new Object();
console.log(obj); //{}
obj.name='蔡徐坤'
复制代码
1.2.2 自定义构造函数创建对象
function Person(uname, age, sex) {
//若果是执行构造函数,这一句就是给当前创建的实例对象设置私有属性和方法。
this.name = uname;
this.age = age;
this.sex = sex;
}
var cxk = new Person("蔡徐坤", "23", "男");
console.log(cxk.name);
复制代码
1.3 new Object()
执行原理
当我们new Object()系统做了什么事情?
1.会在构造函数中自动创建一个空对象,这个对象就是当前类的实例;
2.把构造函数中的this赋值给空对象
3.设置原型链,空对象指向构造函数的原型对象
4.执行构造函数内部的代码(给空对象添加属性)
5.会在构造函数的最后自动添加return this; 返回原始值忽略,返回对象则正常处理
二.面向对象基础(面向对象和构造函数)
2.1 什么是面向对象?
- 自然界的万事万物都可以看作是
对象
;- 根据这些对象的特点又将其划分为不同的
类
=> “类”是对“对象”的一种细分,以及对公共部分的封装。- 类别中派生出来的具体的事务叫做类的
“实例”
。实例既有属于自己私有的东西,也有继承各个类别中的共有信息。面向对象编程
,其实就是掌握“对象”、“类”、“实例”之间的关系、知识。例如:类的封装、继承、多态。
2.2 JS中的内置类
- JS中每一种数据类型都有自己对应的类别:
- 每一个dom元素对象也有自己的类和祖先类(
dir()
查找)
- div元素对象–>HTMLDivElement–>HTMLElement–>Element–>Node–>EventTarget–>Object
- 元素集合–>HTMLCollection–>Object
- 节点集合–>NodeList–>Object
2.3 js自定义类(构造函数)
- 构造函数特点: 1.函数体内使用this关键字,代表了所要生成的对象实例。 2.生成对象,必须使用new 关键字实例化。
- 不管是自定义类还是内置类,都是函数数据类型的;
typeof Array是"function"
- js是基于构造函数(constructor)和原型链(prototype)。
function Person(myName, myAge) {
this.name = myName;
this.age = myAge;
}
Person.prototype = {
say: function () {
console.log("hello world");
},
};
let obj1 = new Person("蔡徐坤", 23);
obj1.say();
let obj2 = new Person("毛不易", 29);
obj2.say();
console.log(obj1.say === obj2.say); // true
复制代码
- 基于instanceof 可以检测当前对象是否属于某个类的实例,返回布尔值:
实例 instanceof 类
- 每一一个对象都有很多属性和方法,在自己堆内存中存储的都是自己的私有属性和方法,基于
__proto__
原型链查找类prototype原型上的都是共有的属性和方法。- 通过
对象.hasOwnProperty(属性)
检测是否为私有属性,是则返回true。- 判断某一个对象类中或原型中是否拥有某一个属性可以用“in”,只要类中或者原型对象中有, 就会返回true
- isPrototypeOf用于判断 一个对象是否是另一个对象的原型
三.原型和原型链
- 函数数据类型:普通函数、类
- 对象数据类型:普通对象、数组对象、正则对象、日期对象、实例对象、函数对象、类的prototype
3.1 原型prototype(原型属性)
- 1 . 每一个函数数据类型(只包括构造函数、普通函数)都具备prototype(原型属性),并且属性值是一个对象;
- 2 . 在
prototype对象
中会存储当前类的公共属性和方法;- 3 . 在
prototype
的堆内存中,若果是浏览器默认为其开辟的堆内存,会存在一个内置的属性constructor构造函数,属性值就是当前类本身。即:
function Fn() {}
f = new Fn();
console.log(Fn === Fn.prototype.constructor);//true
复制代码
3.2 原型链__proto__
-
每一个对象(普通对象、prototype、实例、函数等)都具备内置属性:
__proto__
(原型链属性),属性值是当前实例所属类的原型(即prototype这个堆) -
(对象基类)Object.prototype也是一个对象,他也有
__proto__
,他是指向基类属性值为null。
//实例对象的原型链`__proto__`指向构造函数的Fn.prototype原型对象
console.log(f.__proto__ === Fn.prototype);//true
//构造函数的原型对象的`__proto__`指向Object内置类的原型对象,即可看成所有构造函数都是Object的实例对象
console.log(Fn.prototype.__proto__ === Object.prototype);//true
console.log(Array.prototype.__proto__ === Object.prototype);//true
console.log(Function.prototype.__proto__ === Object.prototype);//true
console.log(String.prototype.__proto__ === Object.prototype); //true
复制代码
3.3 原型链查找机制
它是一种基于
__proto__
向上查找的机制,当我们操作实例的某个属性或者方法的时候,首先找到自己空间中私有的属性或者方法:
1.找到了,则结束查找,使用自己的私有即可;
2.没有找到 则基于__proto__
找所属类的prototype上的公有属性和方法,如果找到了就用这个公有的;
3.如果没找到,基于原型上的__proto__
继续向上查找,一直找到Object:prototype的原型为止,如果在没有,操作的属性或者方法不存在。
例题分析:
3.4 内置原型上扩展方法
1.内置类原型上默认会存有很多方法:Array.prototype、Object.prototype、String.prototype…这些方法实例都可以调用,但是我们也需要向原型上扩展方法,满足开发需求。
2.向原型上扩展方法,调用起来比较方便:
实例.方法();
方法执行的时候,方法中的this指向当前实例;
注意:
1.名字不要重复
2.this是对象数据类型,但是会隐式调用valueOf方法,返回他的原始值
3.如果返回的结果依旧是当前类的实例,那么可以继续调用当前类的其他方法。链式调用
- 对于一个对象来说,他的属性和方法存在
可枚举
的特点:即在for…in…循环时是否可以遍历到,内置原型上自己扩展的方法是可枚举的。
(function () {
console.log("函数已执行");
function handleNum(num) {
num = Number(num);
return isNaN(num) ? 0 : num;
}
Number.prototype.plus = function plus(num) {
handleNum(num);
return this + num;
};
Number.prototype.minus = function minus(num) {
handleNum(num);
return this - num;
};
})();
let n = 10;
let m = n.plus(10).minus(5);
console.log(m); //15, 只有n一直为数子才可以一直调用
复制代码
let obj = {
name: "蔡徐坤",
age: 22,
};
Object.prototype.fun = function () {
console.log("我是蔡徐坤");
};
for (let key in obj) {
//在遍历对象上的属性和方法时,也会遍历原型上添置的属性和方法
console.log(key); // name age fun
//过滤掉原型上的属性和方法
if (obj.hasOwnProperty(key)) {
console.log(key); //name age
}
}
复制代码
3.5 重写内置new和Object.create方法
3.5.1 重写内置new
题1:编写_new实现内置的new Xxx()功能
//构造函数
function Dog(name) {
this.name = name;
}
//添加原型方法
Dog.prototype.bark = function () {
console.log("wangwang");
};
/*
需求: 通过 _new Fn()执行构造函数创造一个实例对象
@params
Func: 要创建的实例对象的所属类(构造函数)
...args: 传给Func函数的实参(ES6中的剩余运算符)
*/
function _new(Func, ...args) {
console.log("进入new函数");
//1.创建一个空对象,这个空对象是当前类的一个实例(对象.__proto__===类.prototype)
/*
let obj = {};
obj.__proto__ = Func.prototype;这个方法ie不兼容__proto__
*/
let obj = Object.create(Func.prototype);
//2.把类当作普通函数执行(this指向的是实例对象)
let result = Func.call(obj, ...args);
/*
...args为ES6中的展开运算符,将args数组展开成每一项。
call方法将Func函数中的this强行修改为第一个参数obj。
*/
//3.返回值问题
/*
函数没有返回值或返回值为基本类型(没啥用),仍然返回创建的实例;
返回值为引用类型值,它将把创建的实例覆盖掉并且返回自己写的这个结果。
*/
if (
(result !== null && typeof result === "object") ||
typeof result === "function"
) {
return result;
}
return obj;
}
let sanmao = _new(Dog, "三毛");
复制代码
// function _new(Func,...args){ }等价于
function _new(Func){
let args=[];
for(let i=1;i<arguments.length;i++){
args.push(arguments[i])
}
}
复制代码
3.5.2 Object.create()方法
Object.create(xxx)创建一个空对象,会把当前的xxx作为这个对象的原型链指向===相当于创建某个类的空实例
Object.create = function create(prototype){
function Fn(){};
Fn.prototype = prototype; // 重定向函数Fn的原型
return new Fn;
}
复制代码
Object.create原始方法
var obj = Object.create(null);
console.log("打印111", obj);
//创建一个空实例并让它的原型链__proto__指向null,里面没有__proto__了,原型链断了
obj.__proto__ = Array.prototype;
console.log(obj);
console.log(obj.slice); // => undefined
console.log(obj.__proto__.slice); // => 找得到
/*
按理说应该能调用数组的方法
但是,var obj = Object.create(null)之后,obj咋啥也没有了
上面这个神操作将它原本的原型链机制破坏掉了,而接下来obj.__proto__ = Array.prototype时只是将__proto__当作一个私有属性啦,
所以根本没法向上查找,当然找不到原本Array.prototype上的方法咯。
*/
复制代码
面试题:Array.push方法
Array.prototype.push = function push(val) {
//this当前操作push的实例
this[this.length-1+1] = val; // 向数组末尾添加元素,在最大原始索引(length-1)上+1
this.length++; // 数组长度增加
return this.length; // 返回数组长度
};
复制代码
let obj = {
2: 3,
3: 4,
length: 2,
push: Array.prototype.push,
};
obj.push(1);
/*
this[this.length] = val; //obj[2]=1
this.length++; //length=3
*/
obj.push(22);
/*
this[this.length] = val; //obj[3]=22
this.length++; //length=4
*/
console.log(obj);
/*
{
2: 1,
3: 22,
length: 4,
push: Array.prototype.push,
};
*/
复制代码
面试题:a=?能打印ok
1.a是对象
a = {
i: 0,
//给当前对象重写私有方法valueOf,这样就不会在原型上找内置方法
valueOf() {
return ++this.i;
},
};
if (a == 1 && a == 2 && a == 3) {
console.log("ok");
}
复制代码
2.a是数组
let a = [1, 2, 3];
a.toString = a.shift;
//每次判断都会调用toString方法,让他重定向为shift,每次判断会删除第一项,并返回删除项
if (a == 1 && a == 2 && a == 3) {
console.log("ok");
}
复制代码
2.利用数据劫持
//数据劫持(劫持对象中的某个属性),在每次获取a值的时候,我们把它劫持到,返回我们需要的值
Object.defineProperty(window, "a", {
get() {
//在每一次获取window.a的时候触发执行;
//返回啥值,本次获取的值就是啥
},
set(val) {
//在每一次给window.a赋值的时候执行
},
});
复制代码
var i = 0;
Object.defineProperty(window, "a", {
get() {
return ++i;
},
});
//每次判断都会调用toString方法,让他重定向为shift,每次判断会删除第一项,并返回删除项
if (a == 1 && a == 2 && a == 3) {
console.log("ok");
}
复制代码
3.6 原型对象重写
浏览器默认为构造函数创建一个原型对象,我们可以通过
Fn.prototype={....}
改写原型,但是一个完整的原型应该包含constructor函数,因此,改写原型的时候要创建一个函数Fn.constructor=Fn
let obj={...}
obj=Object.assign(Fn.prototype,obj)//重定向之前把原始原型和新对象合并一下
Fn.prototype=obj
复制代码
面试题:
function Fn(n, m) {
n = n || 0;
m = m || 0;
this.x = n;
this.y = m;
this.getX = function () {
console.log(this.x);
};
return n + m;
}
Fn.prototype.sum = function () {
console.log(this.x + this.y);
};
Fn.prototype = {
getX: function () {
this.x += 1;
console.log(this.x);
},
getY: function () {
this.y -= 1;
console.log(this.y);
},
};
let f1 = new Fn(10, 20);
let f2 = new Fn();
console.log(f1.getX === f2.getX);
console.log(f1.getY === f2.getY);
console.log(f1.__proto__.getY === Fn.prototype.getY);
console.log(Fn.prototype.getX === f2.getX);
console.log(f1.constructor);
f1.getX();
Fn.prototype.getX();
f2.getY();
Fn.prototype.getY();
f1.sum();
/*
false true true false
ƒ Object() { [native code] }
10 NaN -1 NaN
/*
复制代码
3.7 原型和原型链的一些题目
-
- 写出下面代码执行输出的结果
function C1(name) {
if (name) {
this.name = name;
}
}
function C2(name) {
this.name = name;
}
function C3(name) {
this.name = name || "join";
}
C1.prototype.name = "Tom";
C2.prototype.name = "Tom";
C3.prototype.name = "Tom";
console.log(new C1().name + new C2().name + new C3().name); //Tomundefinedjoin
/*
new C1().name==>//没有传值,不会执行if里面的,所以没有私有属性name,去原型找name,输出tom;
new C2().name==>//没有传值,私有属性name=undefined,输出undefined;
new C3().name==>//没有传值,私有属性name=undefined||join,输出join;
*/
复制代码
- 写出下面代码执行输出的结果
function Foo() {
getName = function () {
console.log(1);
};
return this;
}
Foo.getName = function () {
console.log(2);
};
Foo.prototype.getName = function () {
console.log(3);
};
var getName = function () {
console.log(4);
};
function getName() {
console.log(5);
}
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
复制代码
6.画图计算下面的结果
function fun() {
this.a = 0;
this.b = function () {
alert(this.a);
};
}
fun.prototype = {
b: function () {
this.a = 20;
alert(this.a);
},
c: function () {
this.a = 30;
alert(this.a);
},
};
var my_fun = new fun();
my_fun.b();
my_fun.c();
复制代码
3.8 JavaScript-对象三角恋关系
- 1.每个”构造函数”中都有一个默认的属性, 叫做
prototype
,prototype
属性保存着一个对象, 这个对象我们称之为”原型对象
“;- 2.每个”
原型对象
“中都有一个默认的属性, 叫做constructor
,constructor
指向当前原型对象对应的那个”构造函数
“;- 3.通过
构造函数
创建出来的对象我们称之为”实例对象
“,每个”实例对象
“中都有一个默认的属性, 叫做__proto__
,__proto__
指向创建它的那个构造函数的”原型对象
“;
四.Function和Object内置类的相爱相杀
- 我们先研究一下数组arr = [10,20,30];
- 数组也是对象,所以它内部除了”项”和length以外,还有原型链__proto__,指向的是Array内置数组类的原型prototype.
- 数组类是一个函数,他是Function基类的一个实例对象,作为一个对象,也有__proto__原型链,那Array作为一个类也是一个function,所以有prototype原型对象属性,所以Array的__proto__指向的是Function.prototype.
- 同样,函数类也有原型和原型链,那函数Function的prototype自然也指向Function.prototype原型对象,而函数Function也属于Function自己的实例,所以函数Function的__proto__也指向Function.prototype.(注意:Function.prototype是一个函数)。
接下来该对象基类出现了:
Object作为一个函数类,可以看成是Function的实例对象,它的原型链__proto__自然是指向所属类的构造函数的原型
Function.prototype
,而作为构造函数,它的原型prototype肯定也是指向的自己的prototype对象。
那此时就出现了一个奇怪的现象,Array的prototype的__proto__毫无疑问指向的是Object.Prototype,可Function.prototype的__proto__也指向的是对象类Object.prototype,而Object本身作为一个函数类它的__proto__原型链指向的是Function.prototype.
每个函数都是Function的实例
所以Function自己也是自己的实例
Function instanceof Function //true
函数类的原型链指向函数类自己的原型
Function.__proto__ === Function.prototype //true
Object作为一个类(一个函数),它是Function的一个实例;
Function虽然是一个函数类,但它也是一个对象,所以他也是Object的一个实例;
Object.__proto__.__proto__ === Object.prototype //true
复制代码
在js中的任何实例(任何值【除了值类型的值】)最后都可以基于自己的__proto__找到Object.prototype,也就是所有的值都是Object的实例=>万物皆对象。
总结一下(便于理解):
关于Function:
1.JavaScript中函数是引用类型(对象类型), 既然是对象,所以也是通过构造函数创建出来的,”所有函数”都是通过Function构造函数创建出来的对象.
2.JavaScript中只要是”函数”就有prototype属性,”Function函数”的prototype属性指向”Function原型对象”,很奇怪它是一个函数。
3.JavaScript中只要”原型对象”就有constructor属性,指向所属类;”Function原型对象”的constructor指向它对应的构造函数。
4.Person构造函数是Function构造函数的实例对象, 所以也有__proto__属性,Person构造函数的__proto__属性指向”Function原型对象”
关于Object:
- JavaScript中还有一个系统提供的构造函数叫做Object,只要是函数都是”Function构造函数”的实例对象,所以Object作为基类也是Function的实例对象。
2.只要是对象就有__proto__属性, 所以”Object构造函数”也有__proto__属性, “Object构造函数”的__proto__属性指向创建它那个构造函数的”原型对象”
3.只要是构造函数都有一个默认的属性, 叫做prototype, prototype属性保存着一个对象, 这个对象我们称之为”原型对象”
4.只要是原型对象都有一个默认的属性, 叫做constructor,constructor指向当前原型对象对应的那个”构造函数”
function Person(myName, myAge) {
this.name = myName;
this.age = myAge;
}
let obj1 = new Person("蔡徐坤", 22);
console.log(Function);// ƒ Function() { [native code] }不报错,系统提供了一个构造函数
console.log(Function.prototype);// ƒ () { [native code] }
console.log(Function.prototype.constructor);//ƒ Function() { [native code] }
console.log(Function === Function.prototype.constructor); // true
console.log(Person.__proto__);//ƒ Function() { [native code] }
console.log(Person.__proto__ === Function.prototype); // true
console.log(Function.__proto__);//ƒ Function() { [native code] }
console.log(Function.__proto__ === Function.prototype); // true
console.log(Object);
console.log(Object.__proto__);
console.log(Object.__proto__ === Function.prototype); // true
console.log(Object.prototype);
console.log(Object.prototype.constructor);
console.log(Object.prototype.constructor === Object); // true
console.log(Object.prototype.__proto__); // null
复制代码
函数对象关系:
1.Function函数是所有函数的祖先函数,所有函数都是Function构造函数的实例对象
2.所有的构造函数都有一个prototype属性, 所有prototype属性都指向自己的原型对象
3.所有的原型对象都有一个constructor属性, 所有constructor属性都指向自己的构造函数
4.所有函数都是对象, 包括Function构造函数
5.所有对象都有__proto__属性
6.普通对象的__proto__属性指向创建它的那个构造函数对应的”原型对象”
7.所有对象的__proto__属性最终都会指向”Object原型对象”
8.”Object原型对象”的__proto__属性指向NULL
function Person(myName, myAge) {
this.name = myName;
this.age = myAge;
}
let obj1 = new Person("蔡徐坤", 22);
console.log(Function.prototype.__proto__); //Object
console.log(Person.prototype.__proto__); //Object
console.log(Function.prototype.__proto__ === Person.prototype.__proto__); //true
console.log(Function.prototype.__proto__ === Object.prototype); //true
console.log(Person.prototype.__proto__ === Object.prototype); //true
复制代码