JavaScript面向对象基础 - 函数与对象
★ JavaScript函数基础
初识函数
- 含有默认值的参数
(function greet(name, say = 'Hi,'){
console.log(say + name);
})('ppo!');
- 剩余参数
(function transferParam(num1, ...theNums){
theNums.unshift(num1);
console.log(theNums);
})(1,2,3,4,5,6);
(function transferParam(...theNums){
console.log(theNums);
})(1,2,3,4,5,6);
- 函数的调用
function getSum(){
var sum = 0;
for(var i in arguments){
sum += arguments[i];
}
return sum;
}
console.log(getSum(1,2,3,4,5,6));
- 垃圾回收机制
- 由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。
- 若要在开发中保留局部变量的值。可以利用return返回,或者通过全局变量保存。
var num = (function sum(num){
num += 1;
return num;
})(20);
console.log(num);
var money;
(function total(num){
money = num + 1;
})(20);
console.log(money);
- 内存中的函数对象
- 定义时:
- 创建2个对象:函数对象和作用域链对象
- 函数对象:封装了函数的定义,但暂时不读取函数定义
- 作用域链对象:专门保存函数可用的变量所在位置的对象
- 调用时:
- 创建1个新对象:活动对象
- 活动对象:临时封装本次调用函数时使用的局部变量
- 并将活动对象的引用,压入作用域链对象中。
- 调用后:
- 作用域链中的活动对象引用出栈
- 活动对象及其内部的局部变量一同释放!
- 定义时:
匿名函数
- 函数表达式中省略函数名
var fn = function(num1, num2){
return num1 + num2;
}
console.log(fn(1, 2));
- 自调用方式
var res = (function(num1, num2){
return num1 + num2;
})(1, 2);
console.log(res);
- 处理事件
document.onclick = function(){
alert('你好,世界!');
}
- 箭头函数 - 标准语法
((p1,p2,p3) => {
console.log(p1+ p2+ p3);
})(1,2,3);
- 箭头函数 - 返回值
var sum = ((p1,p2,p3) => {
return p1+p2+p3;
})(1,2,3);
console.log(sum);
- 箭头函数 - 一个参数
(x => {
console.log(x);
})("你好,世界!");
- 箭头函数 - 两个参数
((x, y) => {
console.log(x+ y);
})("你好,", "世界!");
- 箭头函数 - 无参函数
(() => {
console.log("你好,");
})();
(_ => {
console.log("世界!");
})();
回调函数
- 案例
function cal(a, b, fn){
return fn(a, b);
}
function add(num1, num2){
return num1+ num2;
}
function mul(num1, num2){
return num1* num2;
}
console.log(cal(45, 5, add));
console.log(cal(45, 5, mul));
- 数组回调函数API
find()
- 返回数组中满足回调函数的第一个元素的值,否则返回undefinedevery()
- 测试数组的所有元素是否都通过了回调函数的测试some()
- 测试数组中的某些元素是否通过由回调函数实现的测试forEach()
- 对数组的每个元素执行一次提供的函数map()
- 创建一个新数组,其结果是该数组中的每个元素都调用一次提供的回调函数后返回的结果reduce()
- 对累加器和数组中的每个元素(从左到右)应用一个函数,将其减少为单个值reduceRight()
- 接收一个函数作为累加器(accumulator)和数组的每个值(从右到左)将其减少为单个值
var arr = ['a', 'b', 'c'];
arr.map((value, index) => {
console.log(value, index);
});
递归函数
- 阶乘案例
function factorial(n){
if(n== 1){
return 1;
}else{
return n* factorial(n- 1);
}
}
var n= parseInt(prompt('请输入阶乘数:'));
if(isNaN(n)){
console.log('n值输入不合法');
}else{
console.log(factorial(n));
}
- 斐波那契数列第N项的值
var recursion= (n)=> {
if(n< 0){
return '输入的数字不能小于0';
}else if(n== 0){
return 0;
}else if(n== 1){
return 1;
}else if(n> 1){
return recursion(n- 1)+ recursion(n- 2);
}
}
console.log(recursion(10));
闭包函数
- 作用域链
- 由于作用域是相对于变量而言的,而如果存在多级作用域,这个变量又来自于哪里?这个问题就需要好好地探究一下了,我们把这个变量的查找过程称之为变量的作用域链
- 作用域链的意义:查找变量(确定变量来自于哪里,变量是否可以访问)
- 简单来说,作用域链可以用以下几句话来概括:(或者说:确定一个变量来自于哪个作用域)
- 查看当前作用域,如果当前作用域声明了这个变量,就确定结果
- 查找当前作用域的上级作用域,也就是当前函数的上级函数,看看上级函数中有没有声明
- 再查找上级函数的上级函数,直到全局作用域为止
- 如果全局作用域中也没有,我们就认为这个变量未声明(xxx is not defined)
var name = 'sunny';
(()=>{
var name = 'storm';
console.log(name);
})();
var name = 'sunny';
(()=>{
console.log(name);
var name = 'storm';
})();
var name = "sunny";
var f1 = ()=> {
return ()=>{
console.log(name);
}
var name="storm";
}
var nameNew = f1();
nameNew();
var name = "sunny";
var f1 = ()=>{
return {
say:()=>{
console.log(name);
var name = "storm";
}
}
}
var nameNew = f1();
console.log(nameNew.say());
- 闭包函数的实现
- 函数执行完毕后,作用域中保留了最新的timer变量的值
- 闭包函数常应用于模块化开发,防止变量被破坏
var count = ()=>{
var timer = 0;
var c = ()=>{
return timer++;
}
return c;
}
var counter = count();
console.log(counter());
console.log(counter());
console.log(counter());
-
闭包的创建
- 先用外层函数封装一个受保护的局部变量
- 再在内层函数中操作外层函数的变量
- 外层函数将内层函数返回到外部,在外部反复调用
-
闭包的判断
- 函数嵌套!
- 内层函数使用外层函数的局部变量
- 内层函数被返回到外部,在外部调用
-
判断闭包输出
- 同一次外层函数调用返回的内层函数,操作同一个变量
- 外层函数调用了几次,就有几个受保护的变量副本
-
闭包的优缺点
- 优点:即要重用变量,又要保护变量不被污染
- 缺点:占用更多内存空间——因为outer的活动对象无法释放
-
函数的四种调用方式
- 在ES6的箭头函数之前的时代,想要判断一个函数内部的this指向谁,就是根据这四种方式来决定的,函数内部的this跟大小写、书写位置无关
- 函数调用
- 方法调用
- 构造函数调用(new)
- 上下文方式调用(call、apply、bind)
// 函数调用,this指向window
var age = 18;
var person = {
age:15,
say:function(){
console.log('我今年' + this.age + '岁!');
}
}
var sunny = person.say;
sunny();
// 方法调用,this指向对象person
var age = 18;
var person={
age:15,
say:function (){
console.log('我今年' + this.age + '岁!');
}
}
person.say();
// 构造函数调用,this将指向构造函数实例本身
var age=18;
var person={
age:15,
say:function(){
console.log('我今年' + this.age + '岁!');
}
}
new person.say();
//上下文调用方式,有3种,call、apply、bind
function f1(){
console.log(this);
}
//call方法的第一个参数决定了函数内部的this的值
f1.call([1,3,5])
f1.call({age:20,height:1000})
f1.call(1)
f1.call("abc")
f1.call(true);
f1.call(null)
f1.call(undefined);
//上述代码可以用apply完全替换
//总结:
//call方法的第一个参数:
//1、如果是一个对象类型,那么函数内部的this指向该对象
//2、如果是undefined、null,那么函数内部的this指向window
//3、如果是数字-->this:对应的Number构造函数的实例
// --> 1 --> new Number(1)
// 如果是字符串-->this:String构造函数的实例
// --> "abc" --> new String("abc")
// 如果是布尔值-->this:Boolean构造函数的实例
// --> false --> new Boolean(false)
- bind()函数
// 普通方法调用
var person = {
age:18,
run : function(){
console.log(this); //this指向person
var _that=this;
setTimeout(function(){
console.log(this.age); //this指向window
console.log(_that.age);
},50);
}
}
person.run();
// 通过执行了bind方法,匿名函数本身并没有执行,只是改变了该函数内部的this的值,指向person
var person = {
age:18,
run : function(){
console.log(this); // this指向person
setTimeout((function(){
console.log(this.age);
}).bind(this),50); // 绑定this指向person
}
}
person.run();
// bind函数基本用法
function speed(){
console.log(this.seconds);
}
speed({ seconds:100 });
// 执行了bind方法之后,产生了一个新函数,
// 这个新函数里面的逻辑和原来还是一样的,唯一的不同是this指向{ seconds:100 }
var speedBind = speed.bind({ seconds:100 });
speedBind(); //100
// bind函数常规写法
(function eat(){
console.log(this.seconds);
}).bind({ seconds:360 })()
// bind函数案例
var obj={
name:"西瓜",
drink:(function(){
//this指向了:{ name:"橙汁" }
console.log(this.name);
}).bind({ name:"橙汁" })
}
obj.drink(); //"橙汁"
var p10={
height:88,
run:function(){
//this
setInterval((function(){
console.log(this.height); //88
}).bind(this),100)
}
}
p10.run();
// 手写bind函数
Function.prototype._bind = target => {
// 这里的this指向函数实例
// target表示新函数的内部的this的值
// 利用闭包创建一个内部函数,返回那个所谓的新函数
return () => {
this.call(target);
}
}
function person(){
console.log(this);
}
person();
var sunny = person.bind({age:18});
sunny();
★ JavaScript对象基础
初识对象
-
面向对象的特征
- 封装性、继承性、多态性
- 对象是键值对的集合:对象是由属性和方法构成的 (ps:也有说法为:对象里面皆属性,认为方法也是一个属性)
-
对象属性操作
var student = {
name: 'sunny',
age: 20,
number: '11034P',
department: 'newEnergy',
score: function(){
return '成绩查询系统!';
},
job: function(){
return '就业查询系统!';
}
}
var arr = [];
// .语法
// .语法后面不能使用js中的关键字、保留字(class、this、function。。。)
// .语法后面不能使用数字
// .语法后面可直接执行方法函数
console.log(student.name);
console.log(student.score());
// []语法
// []内部必须用引号引入变量名 (student['number']
// ["class"]、["this"]等保留字都可以随意使用
// [0]、[1]、[2]也可以使用 ? 为什么obj[3]==obj["3"]
// 甚至还可以这样用:["[object Array]"] jquery里面就有这样的实现
// 也可以这样用:["{abc}"] 给对象添加了{abc}属性
console.log(student['age']);
console.log(student['job']);
// 添加属性
student['[object Array]'] = 240;
student['{object}'] = {'arts':85, 'sports': 95};
for(key in student){
arr.push(key);
}
console.log(student);
console.log(arr);
arr = [];
// 修改属性
student.gender = 'man';
student['direction'] = function(){
return '人工智能与图形操作';
}
for(key in student){
arr.push(key);
}
console.log(student);
console.log(arr);
arr = [];
// 删除属性
delete student.number;
for(key in student){
arr.push(key);
}
console.log(student);
console.log(arr);
arr = [];
- 获取对象长度的方法
- 对象的长度不能用.length获取
- Object具有一个keys属性,可以返回json对象的key的数组
- Object可以使用in运算符
var student = {
name: 'sunny',
age: 20,
number: '11034P',
department: 'newEnergy',
score: function(){
return '成绩查询系统!';
},
job: function(){
return '就业查询系统!';
}
}
var arr = [];
arr = Object.keys(student);
console.log(arr.length);
arr = [];
for(key in student){
arr.push(key);
}
console.log(arr.length);
arr = [];
console.log('name' in student);
- 对象的浅拷贝与深拷贝
// 浅拷贝
var student = {
name: 'sunny',
age: 20,
number: '11034P',
department: 'newEnergy',
score(){
return '成绩查询系统!';
},
job(){
return '就业查询系统!';
}
}
var simple = obj=>{
let newObj = {};
for(let key in obj){
newObj[key] = obj[key];
}
return newObj;
}
var sunny = simple(student);
console.log(sunny);
// 深拷贝
var student = {
name: 'sunny',
age: 20,
number: '11034P',
department: 'newEnergy',
score: {
arts: 95,
sports: 85,
program: 95
},
job(){
return '就业查询系统!';
}
}
var deepCopy = obj=>{
var newObj = {};
for(let key in obj){
newObj[key] = (typeof obj[key] === 'object') ? deepCopy(obj[key]) : obj[key];
}
return newObj;
}
var sunny = deepCopy(student);
console.log(sunny);
构造函数
-
构造函数的概念
- 任何函数都可以当成构造函数
- 只要把一个函数通过new的方式来进行调用,我们就把这一次函数的调用方式称之为:构造函数的调用
- new CreateFunc(); 此时CreateFunc就是一个构造函数
- CreateFunc(); 此时的CreateFunc并不是构造函数
- new Object()等同于对象字面量{}
-
构造函数的执行过程
var p1=new Person();
- 创建一个对象 (我们把这个对象称之为Person构造函数的实例)-
_p1
- 创建一个内部对象,
this
,将this指向该实例(_p1) - 执行函数内部的代码,其中,操作this的部分就是操作了该实例(_p1)
- 返回值:
- 如果函数没有返回值(没有return语句),那么就会返回构造函数的实例(_p1)
- 如果函数返回了一个基本数据类型的值,那么本次构造函数的返回值是该实例(_p1)
- 如果函数返回了一个引用数据类型的值,那么本次函数的返回值就是该值
-
创建构造函数
var student = {};
student.name = 'sunny';
- 内置的构造函数
var obj = new Object();
var num = new Number();
var str = new String();
var now = new Date();
var arr = new Array();
var reg = new RegExp();
var bool = new Boolean();
var func = new Function();
var img = new Image();
console.log(obj);
console.log(obj.constructor);
console.log(num);
console.log(num.constructor);
console.log(str);
console.log(str.constructor);
console.log(now);
console.log(now.constructor);
console.log(arr);
console.log(arr.constructor);
console.log(reg);
console.log(reg.constructor);
console.log(bool);
console.log(bool.constructor);
console.log(func);
console.log(func.constructor);
console.log(img);
console.log(img.constructor);
- 自定义构造函数
- 构造函数的命名推荐采用帕斯卡命名规则,即所有的单词首字母大写。
- 在构造函数内部,使用this来表示刚刚创建的对象。
var Computer = function(memory, storage, videoMemory, run){
this.memory = memory;
this.storage = storage;
this.videoMemory = videoMemory;
this.run = run;
this.introduce = function(){
return this.memory;
}
}
var noteBook = {
memory: '16GB',
storage: '2TB',
videoMemory: '4GB',
run: function(){
return '你好,世界!';
}
}
var Dell = new Computer(noteBook.memory, noteBook.storage, noteBook.videoMemory, noteBook.run);
console.log(Dell);
console.log(Dell.run());
console.log(Dell.introduce());
- 私有成员
- 在构造函数中,使用var关键字定义的变量称为私有成员
- 在实例对象后无法通过“对象成员”的方式进行访问
- 但是私有成员可以在对象的成员方法中访问
var Computer = function(memory, storage, videoMemory, run){
var processor = 'Core i7-4700MQ';
this.memory = memory;
this.storage = storage;
this.videoMemory = videoMemory;
this.run = run;
this.introduce = function(){
return this.memory + processor;
}
}
var noteBook = {
memory: '16GB',
storage: '2TB',
videoMemory: '4GB',
run: function(){
return '你好,世界!';
}
}
var Dell = new Computer(noteBook.memory, noteBook.storage, noteBook.videoMemory, noteBook.run);
console.log(Dell);
console.log(Dell.processor);
console.log(Dell.run());
console.log(Dell.introduce());
- 构造函数中的return关键字
- 构造函数中,return返回一个数组或对象等引用类型数据,则构造函数会直接返回该数据,而不会返回原来创建的对象。
- 如果返回的是基本类型数据,则返回的数据无效,依然会返回原来创建的对象。
- 总结:构造函数中的return只能返回引用类型的数组或对象,有合规的返回值返回数据,没有就返回本身的对象数据
// 返回值是引用类型数据,返回对象本身
var Computer = function(memory, storage, videoMemory, run){
var processor = 'Core i7-4700MQ';
this.memory = memory;
this.storage = storage;
this.videoMemory = videoMemory;
this.run = run;
this.introduce = function(){
return this.memory + processor;
}
return {say:'你好,世界!'};
}
var Lenovo = new Computer();
console.log(Lenovo);
// 返回值是基本类型数据,返回数据无效,返回原来创建的对象
var Computer = function(memory, storage, videoMemory, run){
var processor = 'Core i7-4700MQ';
this.memory = memory;
this.storage = storage;
this.videoMemory = videoMemory;
this.run = run;
this.introduce = function(){
return this.memory + processor;
}
return 0;
}
var Lenovo = new Computer();
console.log(Lenovo);
内置对象
- 定义:内置对象就是指这个语言自带的一些对象,供开发者使用,这些对象提供了一些常用的或是最基本而必要的功能。
Arguments
函数参数集合Array
数组Boolean
布尔对象Date
日期时间Error
异常对象Function
函数构造器Math
数学对象Number
数值对象Object
基础对象RegExp
正则表达式对象
String对象
- String()对象
length
- 获取字符串的长度charAt(index)
- 获取index位置的字符,位置从0开始计算indexOf(searchValue)
- 获取searchValue在字符串中首次出现的位置,没有返回-1lastIndexOf(searchValue)
- 获取searchValue在字符串中最后出现的位置,没有返回-1substring(start[,end])
- 截取从start位置到end位置之间的一个子字符串substr(start[,length])
- 截取从start位置开始到length长度的子字符串toLowerCase()
- 获取字符串的小写形式toUpperCase()
- 获取字符串的大写形式split(separator[,limit])
- 使用separator分隔符将字符串分割成数组,limit用于限制数量replace(str1,str2)
- 使用str2替换字符串中的str1,返回替换结果
var userName = function(name){
var flag = 0;
return function(){
name.length<3 || name.length>15 ? console.log('用户名长度必须在3~10之间') :
name.toLowerCase().indexOf('admin') !== -1 ? console.log('用户名中不能出现"admin"敏感词') :
flag = 1;
return flag;
}
}
var newName = userName('dministrator');
console.log(newName());
Number对象
- Number()对象
MAX_VALUE
- 在JavaScript中所能表示的最大数值(静态成员)MIN-VALUE
- 在JavaScript中所能表示的最小正值(静态成员)toFixed(digits)
- 使用定点表示法来格式化一个数值
var num = 12345.6789;
num.toFixed(); // 12346
num.toFixed(1); // 12345.7
num.toFixed(6); // 12345.678900
Number.MAX_VALUE; // 1.7976931348623157e+308
Number.MIN_VALUE; // 5e-324
Math对象
- Math()对象:专门封装数学计算常用常量和计算方法的全局对象,Math没有构造函数,不能new!所有API都直接用Math.xxx,
Math.PI()
- 获取圆周率,结果为3.141592653589793Math.abs()
- 获取x的绝对值,可传入普通数值或是用字符串表示的数值Math.max([value1[,value2,...]])
- 获取所有参数中的最大值Math.min([value1[,value2,...]])
- 获取所有参数中的最小值Math.pow(base,exponent)
- 获取基数(base)的指数(exponent)次幂Math.sqrt(x)
- 获取x的平方根Math.ceil(x)
- 获取大于等于x的最小整数,即向上取整Math.floor(x)
- 获取小于等于x的最大整数,即向上取整Math.round(x)
- 获取x的四舍五入后的整数值Math.random()
- 获取大于或等于0.0且小于1.0的随机数
// 随机数函数
var randomNum= function(min, max){
if(arguments.length== 2){
return Math.floor(Math.random()* (max- min+ 1)+ min);
// return parseInt(Math.random()* (max- min+ 1)+ min);
}else if(arguments.length== 1){
return Math.ceil(Math.random()* min);
}else{
return Math.ceil(Math.random()* 100);
}
}
console.log(randomNum());
// 获得数组最大值
Math.max.apply(Math,[1,2,3,4]);
Date对象
- Date()对象:封装一个1970年元旦至今的毫秒数(从1970年1月1日0点0分0秒到当前时间的毫秒差),提供了对时间操作的方法
- get方法用来获得分量的值
getFullYear()
- 获取表示年份的数字,如2020getMonth()
- 获取月份,范围0~11(0表示一月,1表示二月,以此类推)getDate()
- 获取月份中的某一天,范围1~31getDay()
- 获取星期,范围0~6(0表示星期一,1表示星期二,以此类推)getHours()
- 获取小时数,返回0~23getMinutes()
- 获取分钟数,范围0~59getSeconds()
- 获取秒数,范围0~59getMilliseconds()
- 获取毫秒数,范围0~999getTime()
- 获取从1970-01-01 00:00:00 距离Date对象所代表时间的毫秒数
- set方法用来设置分量的值
setFullYear(value)
- 设置年份setMonth(value)
- 设置月份setDate(value)
- 设置月份中的某一天setHours(value)
- 设置小时数setMinutes(value)
- 设置分钟数setSeconds(value)
- 设置秒数setMilliseconds(value)
- 设置毫秒数setTime(value)
- 通过从1970-01-01 00:00:00 计时的毫秒数来设置时间
- get方法用来获得分量的值
- 日期转字符串
var date = new Date();
date.toString()
- GMT格式显示date.toLocaleString()
- 操作系统当地时间格式,包含时间和日期date.toLocaleDateString()
- 以操作系统当地时间格式,仅包含日期date.toLocaleTimeString()
- 以操作系统当地时间格式,仅包含时间
- Date计算
- 两日期对象可直接相减,结果是毫秒差!
- 对每个分量做加减:3步: get分量值,做加减,set回去
- 一步概括:date.setXXX(date.getXXX()+/-n);
// 当前系统时间
var date = new Date();
// 2012年10月13日12时26分35秒
var date = new Date(2012, 10, 13, 12, 26, 35);
// 2012年10月有多少天
var max = new Date(2012, 10, 0).getDate();
// 获取当前月份
var nowMonth = new Date().getMonth()+ 1;
// 将毫秒数转化为时间
var date = new Date(1499996760000);
var dateTime = date.toLocaleString();
// 1970年1月1日午夜以来的毫秒数
new Date().getTime(); // 提倡使用的
new Date().valueOf();
Date.now(); // 直接使用的
+new Date(); // 相当于隐式转换,将该元素转换成Number类型,如果转换失败,那么将得到 NaN
new Date()* 1; // +new Date() 将会调用 Date.prototype 上的 valueOf() 方法,根据MDN,Date.prototype.value方法等同于Date.prototype.getTime()
// 实时时间
setInterval(
_=> {
var date = new Date();
var formate = (date=> {
var week = ['日', '一', '二', '三', '四', '五', '六'];
return date.getFullYear()+ '年'+
(date.getMonth()+ 1)+ '月'+
date.getDate()+ '日'+' ' +
'星期'+ week[date.getDay()]+ ' '+
date.toLocaleTimeString();
})(date);
console.log(formate);
},1000);
错误处理与代码调试
-
错误类型
Error
- 表示普通错误,其余6种类型的错误对象都继承自该对象EvalError
- 调用eval()函数错误,已经弃用,为了向后兼容,低版本还可以使用RangeError
- 数值超出有效范围,如"new Array(-1)"ReferenceError
- 引用了一个不存在的变量,如"var a=1;a+b;"(变量b未定义)SyntaxError
- 解析过程语法错误,如"{;}" "if()" "var a=new;"TypeError
- 变量或参数不是预期类型,如调用了不存在的函数或方法URIError
- 解析URI编码出错,调用encodeURI()、escape()等URI处理函数时出现
-
错误处理:发生错误时,保证程序不中断
try{
可能发生错误的代码
}catch(err){//err中发生错误时会自动收到创建的error对象
err.message: 保存了错误的详细原因
err.name: 保存了错误对象的名称
如果发生错误,才执行的错误处理代码
}[finally{
无论是否发生错误,都执行的代码
*一定会在退出*之前*执行*
一般释放用过的对象资源:xxx=null
}]
- 手动抛出错误对象
try{
// 创建错误对象
var err = new Error('自定义错误信息');
// 抛出错误对象
// 也可以与上一行合并为:throw new Error('自定义错误信息');
throw err;
}catch(err){
// 输出结果,自定义错误信息
console.log(err.message);
}
-
执行效率
- 如果可以提前预知错误的原因:建议使用if代替try catch
- try中尽量少的包含代码,try的执行效率低,且多创建一个error对象
-
Source面板调试工具
Watch
- 可以对加入监听列表的变量进行监听Call Stack
- 函数调用堆栈,可以在代码暂停时查看执行路径Scope
- 查看当前断点所在函数执行的作用域内容Breakpoints
- 查看断点列表XHR Breakpoints
- 请求断点列表,可以对满足过滤条件的请求进行断点拦截DOM Breakpoints
- DOM断点列表,设置DOM断点后满足条件时触发断点Global Listeners
- 全局监听列表,显示绑定在window对象上的事件监听Event Listener Breakpoints
- 可断点的事件监听列表,可以在触发事件时进入断点
原型和原型链
-
原型
- 原型(prototype):保存所有子对象共有成员的对象
- 每个构造函数都有一个原型属性,引用了该构造函数对应的原型对象
- 由构造函数创建的每个对象中都有一个__proto__属性,指向构造函数的原型对象
- 在访问子对象的成员时,优先在成员本地找,找不到,再去构造函数的原型中查找
-
创建原型
- 创建空对象
- 调用构造函数,为新对象添加成员
- 设置当前对象的__proto__属性为构造函数的原型对象
- 每个对象自有的成员,放在构造函数中
- 所有子对象共有的成员,放在原型对象中
-
原型链
- 由各级对象的__proto__逐级向上引用形成的多级继承关系
- 所有的对象都有一个__proto__属性,指向自己的父对象
-
原型相关API
- 判断一个属性是否可用
- in关键字:
"属性名" in 对象
- 如果"属性名"在当前对象的原型链中,返回true
- 如果在整条原型链上都没找到,返回false
- 使用===undefined:说明不包含
- 简写为
(!对象.成员名)
- 简写为
- 判断是否自有属性:
obj.hasOwnProperty("成员");
- 如果obj本地有指定"成员",则返回true,没有返回false
- 判断共有属性:
- 不是自有的,还要存在于原型链上
if(!obj.hasOwnProperty("成员")&&"成员" in obj)
- in关键字:
- 获得任意对象的__proto__属性:
- 获得父级对象
var 父对象=Object.getPrototypeOf(对象);
- 判断父对象是否处在子对象原型链的上级:
父对象.isPrototypeOf(子对象);
- 只要父对象在子对象的原型链上,就返回true,否则返回false
- 删除对象中的属性:
delete 对象.成员
- 只能删除自有的成员
- 只有var声明的全局变量不让delete
- 使用window.或window[""]增加的全局成员可以delete
- 判断一个对象是数组的几种方法
Array.prototype.isPrototypeOf(obj)
- 如果返回true,说明是数组,否则不是
obj instanceof Array
- 对象 instanceof 类型名,返回true,说明是数组
- instancof会查找原型链上所有原型的构造函数
Object.getPrototypeOf(obj)==Array.prototype
Object.prototype.toString.call(arr)
- override,子对象觉得父对象的成员不好用
- 可自己定义同名的自有成员覆盖父对象
Object.prototype.toString.apply(arr)
- call和apply: 在调用方法的一瞬间,替换调用方法的对象
Array.isArray(obj)
- 专门判断对象是否是数组!
- 判断一个属性是否可用
继承
-
概念
- 通过【某种方式】让一个对象可以访问到另一个对象中的属性和方法,我们把这种方式称之为继承
并不是所谓的xxx extends yyy
- 有些对象会有方法(动作、行为),而这些方法都是函数,如果把这些方法和函数都放在构造函数中声明就会导致内存的浪费
- 通过【某种方式】让一个对象可以访问到另一个对象中的属性和方法,我们把这种方式称之为继承
-
继承的第一种方式:原型链方法继承
var Person = function(){
};
Person.prototype.say=function(){
console.log("你好")
}
var sunny = new Person();
console.log(sunny.say());
- 继承的第二种方式:原型链继承
// 一般情况下,应该先改变原型对象,再创建对象;
// 一般情况下,对于新原型,会添加一个constructor属性,从而不破坏原有的原型对象的结构;
var Person = function(){};
Person.prototype = {
//切记不能忘记
constructor:Person,
say:function(){
console.log("你好");
},
run:function(){
console.log("正在进行百米冲刺");
}
}
var sunny = new Person;
sunny.say();
console.log(Person.prototype.constructor === Person);
- 继承的第三种方式:拷贝继承(混入继承:mixin)
// 由于拷贝继承在实际开发中使用场景非常多,所以很多库都对此有了实现
// jquery:$.extend
var Person = {
hear: 'black',
skin: 'yellow',
eye: 'brown',
}
var extend = function (source){
let target = {};
for(let key in source){
target[key] = source[key];
}
return target;
}
var sunny = extend(Person);
sunny.hear = 'white';
console.log(sunny);
// es6中有了 <对象扩展运算符> 仿佛就是专门为了拷贝继承而生
var Person = {
hear: 'black',
skin: 'yellow',
eye: 'brown',
}
var target = {...Person};
console.log(target);
- 继承的第四种方式:原型继承 (道格拉斯在蝴蝶书中提出来的)
var Person = {
hear: 'black',
skin: 'yellow',
eye: 'brown',
}
var sunny = Object.create(Person);
console.log(sunny.__proto__);
- 继承的第五种方式:借用构造函数实现继承
var Animal = function(name, age){
this.name = name;
this.age = age;
}
var Person = function(name, age, address){
Animal.call(this, name, age);
this.address = address;
return this.age;
}
console.log(Person('dog', '18', '不告诉你!'));