一 类和对象概念
1)面向对象?
面向对象是一种编程思想,里面有两个非常重要的概念:类,对象。
类:是抽象地,不具体的,类别就是有相同特征的一类事物。JS中有很多的类,我们也可以自己定义类。
对象:通过类创建出对象,是具体的。JS中也有很多的对象,我们也可以自己去创建对象。
2) 对象?
对象中有很多的静态特征,通常使用变量来描述,此时变量也叫属性。
对象中有很多的动态特征,通常使用函数来描述,引时函数也叫方法(属性)。
对象是属性的无序集合(操作(CRUD)集合)。
3)类?
JS中提供很多类,如Number, String, Boolean, Object, Math, Date, ....
通过类创建出对象,使用new运算符来创建,如: let d = new Date();
new一个类就可以创建出一个对象。
4)三条定律,两条链:
定律一:一切都是对象(数组,函数,对象,基本数据类型在特定场合下也是对象)
定律二:所有的对象都是通过类(函数在某些场合下面也是类)来创建的
定律三:对象是属性的无序集合(操作(CRUD)集合)
两条链:作用域链,原型链。
5)对象是属性的无序集合(对象里面的增删改查):
----创建对象的两种方式:字面量,new
let wc = {
name:"wangcai",
age:100,
say:function(){
console.log("wangwang...")}}
-----访问对象中的属性:通过点运算符,通过【“”】语法访问对象中的属性
!请注意一种特殊的情况:如果属性名是一个变量:只有一种方式来获取对象中的属性:【】注意里面不用加引号
!给一个对象增加属性:通过点运算符,通过【“”】语法访问对象中的属性。注意如果添加了同名的属性,同名属性将会被覆盖
!设置对象的属性(精细化设置):configuration:是否删除;writable:是否可以修改属性值;enumerable:是否可以枚举;value:表示属性值,没有给定属性值,默认值是:undefined;注意,如果对象中没有这个属性,精细化设置属于添加了这个新的属性。
!删除对象中的属性
delect 对象名.属性名(删除对象中的某一属性);默认情况下是可以修改对象中的属性的,枚举:用for in来遍历属性
特殊例子删除属性: a和b都是全局变量,全局变量是作为window的属性
var a = 110;
b = 666;
delete a; // 删除window这个对象上的a属性
delete b; // 删除window这个对象上的b属性
console.log(a) // 110 a没有被删除
console.log(b) // ReferenceError: b is not defined b被删除了
注意:加var的全局变量是不能被删除的,没有var的全局变量是可以被删除的
注意: 如果使用了var,相当于给window这个对象的a属性,设置了configurable:false
!枚举对象中的属性
举个枚举对象中属性的例子:for 。。in
for(item in wc){
console.log(item)
}
举个枚举对象中属性的例子Object.keys(对象名):遍历对象的属性,不能找值,只在自己内部找,而且不能遍历不可枚举的属性
console.log(Object.keys(wc)) // ["name", "age", "say"]
value:可以直接修改或者获取。
普及一个小知识:数组可以理解成键值对:
let arr = ["a","b","c"]
{0:"a",1:"b",2:"c"}
for(var i=0; i<arr.length; i++){
console.log(arr[i]) }
接下来讲一下如何精细化设置对象的属性:
1)Object.defineproperty(对象名,“属性名”,{配置表})
2)Object.defineproperty会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
3)Object是一个类 类.xxx 说明xxx是类上的一个属性(静态属性)
4)defineproperty是Object这个类上的静态属性 define是定义的意思 Property
!!注意在设置时的属性名一定要加引号,不然会报错。它会给你当成变量,变量不能精细化设置特征。
!!注意修改属性的特征:一旦设置不能被删除,那么就不能再设置可以被删除了!!
来个标准例子:
let wc = {
name:"wangcai",
age:100,
say:function(){
console.log("wangwang...")}}
精细化设置
Object.defineProperty(wc,"score",{
configurable:false, // wc这个对象上的score这个属性不能被删除
value:100
});
delete wc.score
console.log(wc.score)
举例子定义一个常量,就是不能被修改,不能被删除,
let obj = {};
Object.defineProperty(obj,"PI",{
writable:false, // 不能修改
configurable:false, // 不能删除
value:3.14,
enumerable:true, // 可以枚举
})
console.log(obj.PI)
(6)判断某一属性是否属于某一对象
in 运算符:判断某一个属性是否属于某一个对象。
hasOwnProperty:方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。
in 和 hasOwnProperty的区别?
in先在自己内部找,如果找不到,会去它的原型对象中找。
hasOwnProperty只会在在自己内部找。
运算符:
let wc = {
name:"wangcai",
age:100,
say:function(){
console.log("wangwang...")}}
//console.log("name" in wc) // 键在外面的使用时需要加引号 true
//console.log(wc.hasOwnProperty("say")) // true这俩都可以获得身上的属性
in可以获得原型上的方法:
var arr = ["a","b","c"]
// arr是对象 push是方法 push在arr内部找不到,就去它的原型对象上找
arr.push("d")
console.log("push" in arr) // true
console.log(arr.hasOwnProperty("push")) // false
这一知识点的数组去重和统计数组中元素的个数:
数组去重:
var arr = [1,2,3,3,1,2,4,5,2,4,5,7,5,7,8];
// function fn(arr){
// var newArr = [];
// // 判断数组中的每一个元素是否处理过
// var o = {}; // {"1":true,"2":true,"3":true}
// for(var i=0; i<arr.length; i++){
// let t = arr[i]; // t表示老数组中的每一项元素
// if(o[t]){ }else{
// newArr.push(arr[i])
// o[t] = true;}}
// return newArr; }
// var newArr = fn(arr);
// console.log(newArr) // [1, 2, 3, 4, 5, 7, 8]
统计数组中元素的个数:
var arr = [1,2,3,3,1,2,4,2,6,2];
function fn(arr){
var obj = {};// {"1":2, "2":3, "3":100} {"1":1,"2":1}
for(var i=0; i<arr.length; i++){
var t = arr[i]
// obj[t] = 1 obj[t] = obj[t]+1
if(obj.hasOwnProperty(t)){
obj[t] = obj[t] + 1
}else{
obj[t] = 1;}}
return obj;}
let res = fn(arr)
// res中应该包含每个元素出现多少次 res是一个对象
console.log(res)
二 函数(js中的VIP,我们的重头戏)
函数在JS中比较特殊:
1)前面我们所学的函数,仅仅是把函数当作是一个普通的函数
2)函数在JS中可以当作是一个类(构造器),通常会把函数名(类名)的首字母大写。
函数声明(此时这个函数仅仅是一个普通的函数)
function fn(a,b){
产生一个局部的执行上下文(局部栈,函数栈..)
形参赋值,声明的提升,代码的执行
return a+b;
}
// console.log(fn(1,2))
3)类的作用就是为了创建出一个对象
function CreateDog(name, age){
this.name = name;
this.age = age;}
let wc = new CreateDog("wangcai",100);
console.log(wc.name)
console.log(wc.age)
4)函数的两个角色
把函数当作一个类(构造器):
目的:创建对象
函数有现在有两个角色:
!普通函数-->局部执行上下文
// 把函数当作普通函数来调用
// function fn(a,b){
// return a+b
// }
// 调用 得到函数的返回值 会产生局部的执行上下文
// fn(1,2)
!类(构造器,构造函数)--->new;把函数当作一个类 和 把函数当作普通函数的区别?
当作一个类,需要new,new相比我们之前直接去调用函数有什么区别?
1)new在执行代码时会创建一个对象,2)然后把this指向这个对象,3)最后把对象的地址返回。
// 通常类名的首字母会大写
function CreateDog(name,age){
this.name = name;
this.age = age;}
let wc = new CreateDog("wangcai",1000);
console.log(wc.name)
console.log(wc.age)
注意:
1:把函数当作类
!Fn的首字母大写,不是强制要求,但是是一个行业的默认规则
!它内部会给我们创建出一个对象并返回 不需要我们做
!在每一个类中都有一个this,this就指了创建出来的对象
!new最终肯定要得到一个对象
大的注意事项:return后面的语句不会执行
如果把一个函数当成一个类,那么你再返回一个普通的基本数据类型的数据,相当于没有返回
它内部会返回一个对象
2:如果在一个类中返回一个对象:如果把函数当作类,在类中返回一个对象,最终这个对象会把默认的对象覆盖掉
function Fn(){
this.name = "xxx";
return {
age:666}}
console.log(Fn()); // {age: 666}
let r = new Fn();
console.log(r); // {age: 666}
5)new出来的对象都是独立的:!!!!
// 代码执行到CreateDog时,做几件事:
// 形参的赋值
// 声明的提升
// 代码的执行
// 创建一个空对象
// 让this指向空对象
// 返回这个对象
// let a = 666; // 就是局部执行上下文中的一个局部变量
// this.name = name // 给这个对象添加一个name属性,并赋值
// this.age = age // 给这个对象添加一个age属性,并赋值
// this.say = function(){} // 给这个对象添加一个say属性,并赋值
function CreateDog(name, age){
let a = 666;
this.name = name;
this.age = age;
this.say = function(){
console.log("wangwang...")}}
let wc = new CreateDog("wangcai",100);
console.log(wc.a) // undefined
console.log(wc.name) // wangcai
// this就指向你创建出来的对象
let wc1 = new CreateDog("wc1",1);
let wc2 = new CreateDog("wc2",2);
console.log(wc1) // {name: "wc1", age: 1, say: ƒ}
console.log(wc2) // {name: "wc2", age: 2, say: ƒ}
console.log(wc1 === wc2) // false
// new CreateDog 可以不传参
// new一个类时,可以加(),也可以不加(), 区别是不加()是不能传参
let wc3 = new CreateDog;
console.log(wc3) // {name: undefined, age: undefined, say: ƒ}
instanceof 判断某个对象是否属于某个类(函数,构造器)
function CreateDog(name, age){
this.name = name;
this.age = age;}
let wc = new CreateDog("wangcai",100);
// console.log(wc instanceof CreateDog) // true
6)JS中默认就有很多的类。
如:Number, String, Boolean, Array, Object, Math, Date, RegExp...
基本数据类型也是属于某一个类的对象(实例)
instanceof对于基本数据类型没法检测,typeof可以对基本数据类型进行检测,但是对引用数据类型检测不精确
instanceof和typeof对于数据类型检测都有不足之处。
三 JSON :JavaScript Object Notation JS对象的表示法
// 校验JSON是否合法:http://www.bejson.com/
上网本质:客户端:手机,QQ,浏览器...服务器:计算机,上网就是客户端去请求服务器,服务器返回你需要的数据的过程。
JSON就是客户端与服务器之间通信的一种数据格式。
JSON通常的写法有两种:
数组的形式:[{},{},{}]
对象的形式:{}
1)JSON数据的语法:
1,有键值构成2,键值对与键值对之间使用逗号隔开3,{} 放对象 [] 放数组4,键也必须使用双引号包起来
var Stu = [
{"id":1,"name":"z3","age":4,"score":"10"},
{"id":2,"name":"z4","age":5,"score":"11"},
{"id":3,"name":"z5","age":6,"score":"12"}
]
var obj = {
name:"wangcai",
age:100,
isSleep:true,
say:function(){
}}
2)JSON:JavaScript Object Notation JS对象的表示法
是一种特殊的JS对象,JSON是一种轻量级的数据交换格式。
操作JSON: CRUD操作(增删改查)
var stu = [
{"id":1,"name":"z3","age":4,"score":"10"},
{"id":2,"name":"z4","age":5,"score":"11"},
{"id":3,"name":"z5","age":6,"score":"12"}
]
// 访问
console.log(stu[0])
console.log(stu[0].name)
console.log(stu[0]["name"])
// 修改
stu[0].score = "100"
console.log(stu[0])
// 删除
delete stu[0].score;
console.log(stu[0])
// 遍历
for(var i=0; i<stu.length; i++){
// console.log(stu[i])
for(var item in stu[i]){
console.log(stu[i].age)
}}
2)JSON对象,JSON字符串
JSON有两种形式:JSON字符串:本质是一个字符串,只能通过单引号引起来
JSON字符串:本质是一个字符串,只能通过单引号引起来
JSON对象:比较特殊的js对象
JSON对象
var obj = [{"id":"01","name":"wangcai"}]
JSON字符串
var obj = '[{"id":"01","name":"wangcai"}]'
JSON字符串和JSON对象之间的咋转化
把JSON对象转成JSON字符串
var obj = [{"id":"01","name":"wangcai"}]
let str = window.JSON.stringify(obj)
console.log(str)
console.log(typeof str)//string
把JSON字符串转成JSON对象
var obj = ‘[{"id":"01","name":"wangcai"}]’
let str = window.JSON.parse(obj)
console.log(str)
console.log(typeof str)//Object
四 原型和原型链
propertype 原型
1)没一个函数(函数也是一个对象)都有一个prototype属性。prototype是一个属性名,它的属性值是一个对象,这个对象我们叫原型对象。
2)原型对象上放了很多的公共的属性和方法。
3)每一个原型对象上面必有一个属性叫constructor。construct也是一个属性名,它的属性值是当前函数本身
4)每一个对象上都有一个属性叫__proto__,也是属性名,它指向原型对象
Func是一个类(构造器),本质是一个函数,函数也是对象
每一个函数(函数也是对象)都有一个prototype属性
function Func(){
this.name = "xxx"
}
// obj1中的__proto__指向创建obj1这个对象的类的原型对象。
let obj1 = new Func();
let obj2 = new Func();
Func.prototype.say = function(){
console.log("say...")
}
访问对象中的属性:
访问对象中的属性
// console.log(obj1.name) // 它内部有name属性,就去它内部找
// obj1.say() // 它内部没有say方法,就去它的原型对象中找
// obj2.say() // 它内部没有say方法,就去它的原型对象中找
Object是所有对象的最顶层的类,也叫基类。所以Object.prototype.__proto__指向它自己。
由于指向自己没有任何意义,所以说它的默认值就是null
原型链:如果找一个属性,先在自己内部(私有的)找,如果有,就使用,不再向后找。
如果没有,就沿着__proto__,找类原型对象上的属性,一直向上找,直到找到Object.prototype为止。
// 测试题
console.log(obj1.say === obj2.say) // true
console.log(Func.prototype.say === obj1.say) // true
console.log(obj1.__proto__.say === Func.prototype.say) // true
console.log(obj1.__proto__ === Func.prototype) // true
console.log(obj1.name === obj2.name) // true
console.log(obj1 instanceof Func) /// true
console.log(obj1 instanceof Object) // true
原型和原型链(关于js中内置对象)
内置对象Array(构造器),arr叫对象。
var arr1 = new Array("a","b","c")
var arr2 = new Array("d","e","f")
console.log(Array.prototype === arr1.__proto__) // true
console.log(Array.prototype.push === arr1.push) // true
console.log(arr1 === arr2)//false
console.log(arr1 instanceof Array)//true
console.log(arr1 instanceof Object)//true
原型链:
console.dir(arr1.__proto__)
console.dir(arr1.__proto__.__proto__)
console.dir(arr1.__proto__.__proto__.__proto__) // null
五:this问题
// ------------------ 情况一:在监听器中this表示事件源
// DOM
var btn = document.getElementById("btn")
// btn叫事件源 on是前缀 click中事件类型
// function(){} 事件处理程序(监听器)
btn.onclick = function(){
// alert(this) // [object HTMLButtonElement]
console.log(this)
// alert("登录成功...")
}
//------------------ 情况二:在非严格模式下,this出现在普通的函数中,表示window,在严格模式下,表示undeeind
function f(){
console.log(this) // Window }
f()
function f(){
"use strict"
console.log(this) // undefined}
f()
// ------------------ 情况三:this出现在一个对象的方法中,表示当前这个对象
var obj = {
name:"wangcai",
say:function(){
console.log(this) // {name: "wangcai", say: ƒ}}}
obj.say();
// ------------------ 情况四:this出现在一个类中,表示指向一个新对象
function Func(){
this.name = "xxx";
console.log(this) // {name: "xxx"} }
let obj1 = new Func();
// ------------------ 情况五:在全局作用域下,this表示window
console.log(this) // Window
注意:方法中的this指向不确定,谁调用了方法,this就是谁
注意:不管是私有方法还是公有方法,谁调用了方法,this就是谁
六:关于匿名函数
把一个匿名函数赋给f
// var f = function(){}
g是函数名 对于函数表达式来说,可以指定函数名
// var f = function g(){console.log("haha...")}
// f()
// 不能通过函数表达式的函数名来调用函数
// g() // g is not defined
g是函数名,可以在函数体中使用,可以通过g()调用这个函数,但是没有出口会死循环,把基本数据类型赋值给g没有作用,引用数据类型赋值给g也没有作用。
对于函数表达式来说,它可以有函数名,不能在函数外面通过函数名调用这个函数,可以在函数内部使用函数名或函数调用(死循环),不能给这个函数名重新赋值
七:call方法
------------------------------------------ 初步了解call方法
1)call是函数的原型对象上的方法
2)一个函数.call,可以把这个函数调用了
3)f.call() f中的this表示window
4)f.call(obj) f中的this表示obj
// 需求,想通过obj调用f函数,怎么做?
方式一:把函数作为obj的属性:把这个函数挂在obj的属性上面
// obj.f = f;
// obj.f(); // f....
方式二:不作用obj的属性,用完就删除
// delete obj.f;
// console.log(obj) // {name: "wangcai"}
方式三:使用call 表示让obj去借用f方法 f方法中的this就指向obj
f.call(obj);
console.log(obj) // {name: "wangcai"}
----------------- 在JS中,this的指向可以通过call进行改变
深入了解call方法 call的第一个参数-----------------第一个参数
// fn.call目的是让fn执行,并且改变fn中this的指向
// 在JS中,this的指向可以通过call进行改变
function fn(){
console.log(this)
console.log("fn...")
}
// fn() 它里面的this表示window
// fn.call(); // fn中的this表示window
// fn.call(123) // fn中的this表示Number {123}
// fn.call("hello") // fn中的this表示String {"hello"}
// fn.call(true) // fn中的this表示Boolean {true}
// fn.call({}) // {}
// let obj = {name:"xxx"}
// fn.call(obj) // {name: "xxx"}
// ----------------- 深入了解call方法 call的其它参数
call方法支持传参,从call方法的第2个实参起,就表是实参列表
let r = fn.call(obj,1,2)
console.log(r)
// ----------------- 深入了解call方法 call的其它参数----------------------
// 总结:
// 1)call是函数的原型对象上的属性
// 2)任何一个函数都可以.call
// 3)当一个函数.call时,这个函数被执行,函数中的this就指向了call中的第1个实参
// 4)如果call方法没有实参,这个函数也能执行,只是函数中this表示window(非严格模式下),在严格模式下,是undefined
// 5)如果call的第一个参数是null,非严格模式下,函数中的this表示window,严格模式下,this表示null
// 6)函数.call()时,函数肯定会执行,可以给函数传参,从call的第2个参数起的实参,都会传给这个函数
//手写call原理,这里就不描述了。
八:apply方法
apply和call的唯一区别是传参的区别。call是一个一个传参,apply是通过数组的形式传参。apply方法原理这里不写。
function fn(a, b){
console.log(this);
return a+b;
}
var obj = {name:"xxx"}
// fn.call(obj,11,22)
let res = fn.apply(obj,[11,22])
console.log(res)
九:bind方法
1)和call/apply一样,都可以改变函数中的this。
2)和call/apply不一样,bind不会让前面的函数执行。需要手动调用。
// ----------------- bind的应用场景
<button id="btn">登录</button>// btn叫事件源 click叫事件类型 function(){}叫监听器
var btn = document.getElementById("btn");
// btn.onclick = function(){//注册点击事件
// console.log("....")
// }
JS是单线程,同一时刻只能执行一个任务,他先把同步任务执行完,当异步任务发生,才会执行异步任务。
// console.log("start")
// function fn(){
// console.log("666")
// }
// // 事件是异步任务
// btn.onclick = fn;
// console.log("end")
// function fn(){
// console.log(this)
// }
// btn.onclick = fn;
// 需求:不想让监听器中的this表示事件源,想让this表示obj
// var obj = {name:"xxx"}
// function fn(){
// console.log(this)
// }
// btn.onclick = fn.call(obj) // 此时使用call就不合适了,因为call会立即执行
// // 需求:不想让监听器中的this表示事件源,想让this表示obj
// var obj = {name:"xxx"}
// function fn(){
// console.log(this)
// }
// btn.onclick = function(){
// fn.call(obj)
// }
需求:不想让监听器中的this表示事件源,想让this表示obj
var obj = {name:"xxx"}
function fn(){
console.log(this)
}
btn.onclick = fn.bind(obj);
bind的原理这里不描述了
十:判断一个属性是否是公有属性(面试题中提及)
一个面试题:
function fn1(){console.log(1)}
function fn2(){console.log(2)}
fn1.call(fn2); //1
fn1.call.call(fn2); //2
Function.prototype.call(fn1) //无
Function.prototype.call.call(fn1) //1
十一:继承和原理在企业面试题中提及:
求数组中最大最小值的四种方法:
基于原型链继承:
基于call或者apply继承:
组合继承:
完美继承:
封装一个方法实现继承:
new原理实现:
instanceof原理实现:
call原理:
apply原理:
bind原理:
myflat原理:
push原理:
// push的原理 Array.prototype.push = function(value){
// this[this.length] = value;
// this.length++;
// return this.length;
// }