新增常识/要求
- 书写格式,符号前后必须空格;块级注释 每行前面要有星号;每个逗号后面给个空格
- 方法名:第一个单词小写,后头的首字母大写
!= 不相等
!== 不全等
学习前的硬知识
▲5大主流浏览器 内核
- ie —— trident (三叉戟
- chrome —— webkit blink
- safari —— webkit
- firefox —— gecko (壁虎
- opera —— presto (急板的 快速的
渐进式 web app
ECMA——欧洲计算机制造联合会
(European computer manufactures association )
ECMA - 262 脚本语言的规范
ES5 ES6:规范化脚本语言
解释型语言和编译型语言
day1
目标
- 掌握变量的声明和使用方式
- 理解数据类型的概念
- 掌握不同的数据类型数据的书写方式和分别
- 掌握不同数据类型之间相互转化的方式
- 能够知道解释性语言和编译型语言的特点
- 能够知道标识符不能是关键字或保留字
1.1 JS三部分组成
-
ECMAScript——JavaScript语法
语法、变量、关键字 保留字 值
原始类型,引用类型运算 对象 继承 函数 -
DOM document object model W3C (操作HTML文档
-
BOM browser object model (滚动条 窗口宽高 事件 事件冒泡、捕获 键盘、鼠标事件 正则
1.2 变量
1. 同时声明多个变量——逗号隔开
2. 声明变量特殊情况
情况 | 说明 | 结果 |
---|---|---|
var age ; console.log (age); | 只声明 不赋值 | undefined |
console.log(age) | 不声明 不赋值 直接使用 | 报错 |
age = 10; console.log (age); | 不声明 只赋值 | 10 |
3. 交换两个变量
- 使用一个 临时变量 用来做中间存储
- 加过来 再减去的方法 (笔试)
var a = 2;
var b = 4;
a = a + b; //6
b = a - b; //6-4=2
a = a - b; // 6-2=4
1.3 数据类型
1.3.1 简单数据类型(基本数据类型)
原始值—— 基本类型 (存在栈内存)
- number
- string
- boolean
- undefined
- null (空值 初始化组件 函数 销毁函数 占位
1.数字型
-
JS中八进制前面加,十六进制前面加0x
-
数字型三个特殊值
① infinity 无穷大
② - infinity 无穷小
③ NaN 非数 -
isNaN( )
用来判断一个变量是否为非数字的类型,返回 true 或 false
Number转换过来的值是不是NaN
Number(值) --> 与NaN进行比较 --> bool ;
2.字符串型
- 字符串引号嵌套
引号嵌套双引号 ,或者用双引号嵌套单引号 (外双内单,外单内双) - 字符串拼接
字符串 + 任何类型 = 拼接之后的新字符串
+ 号总结口诀:数值相加 ,字符相连
1.3.2 复杂数据类型——引用值 (存在堆内存
- object
- array
- function
- date
- RegExp 正则
内存之栈内存与堆内存
和Java那个应该是一样的,要去翻一翻Java那个
1.4 获取变量数据类型
typeof( )
注意:
- 任何typeof再typeof的都是string
- 未被定义的变量,如果你console.log的话会报错,但是有一种情况,你把它放到typeof里头是不会报错的,typeof会报undefined (而typeof的输出类型是字符串)
1.5 数据类型转换
1.5.1 转换为数字型(重点)
方式 | 说明 | 案例 |
---|---|---|
parseInt(string) | 将string类型转成整数数值型 | parseInt(‘78’) |
parseFloat(string) | 将string类型转成浮点数值型 | parseInt(‘78.43’) |
Number() 强制转换函数 | 将string类型转成数值型 | parseInt(‘12’) |
js隐式转换 (- * /) | 利用算术运算 隐式转换为数值型 | parseInt(‘78’) |
显示类型转换 Number( )
var a = '123';
console.log(Number(a) +"--"+typeof(Number(a))); // 123--number
var b = 'abc';
console.log(Number(b) +"--"+typeof(Number(b))); // NaN--number
var c = '123abc';
console.log(Number(c) +"--"+typeof(Number(c))); // NaN--number
var d = true;
console.log(Number(d) +"--"+typeof(Number(d))); // 1--number
var e = false;
console.log(Number(e) +"--"+typeof(Number(e))); // 0--number
var f = null;
console.log(Number(f) +"--"+typeof(Number(f))); // 0--number
var g = undefined;
console.log(Number(g) +"--"+typeof(Number(g))); // NaN--number
显示类型转换 parseInt( )
var d = true;
console.log(parseInt(d) +"--"+typeof(parseInt(d))); // NaN--number
var e = false;
console.log(parseInt(e) +"--"+typeof(parseInt(e))); // NaN--number
var f = null;
console.log(parseInt(f) +"--"+typeof(parseInt(f))); // NaN--number
var g = undefined;
console.log(parseInt(g) +"--"+typeof(parseInt(g))); // NaN--number
var h = NaN;
console.log(parseInt(h) +"--"+typeof(parseInt(h))); //NaN--number
console.log(parseInt('abc123'); // NaN
console.log(parseInt('123abc'); // 123
- 它不管那么多,他只想转换成整型,所以他一定是跟数字相关的,他不会去把布尔类型转换成1
- parseInt从第一位开始看只读取数字,如果第一位不是数字就判定为NaN;小数会被丢掉
▲ parseInt(a,radix)两个参数的方法。 radix基底——进制数
` 基底范围 2~36
当 基底为0时 相当于求整数,一个参数的
parseInt(10,2); 2进制的10 转换成 10进制对应的数是多少?2
parseInt(11,16); 16进制的11 转换成 10进制对应的数是多少? 17
显示类型转换 parseFloat( )
var num = parseFloat('3.1465925');
console.log(num.toFixed(2)); // 四舍五入 3.15
toFixed( ) 保留几位小数
隐式类型转换
var a = '123';
a++;
console.log(a);// 124
var b = '3' * 2;
console.log(b); // 6
var c = '1' > 2; //number
console.log(c); // false
var d = 1 > '2'; //number
console.log(d); // false
var f = 1 == '1'; //number
console.log(f); // true
var g = 1 != '2'; //number
console.log(h); // true
var e = 'd' > 'g'; //ascii
console.log(e);
var j = 2 > 1 > 3; // 1>3 按顺序从左到右比较
console.log(j); // false
var k = 2 > 1 == 1; // 1==1
console.log(k); // true
var k = 2 > 1 == 1; // 1==1
console.log(k); // true
var a1 = undefined < 0;
console.log(a1); // false
var a2 = undefined > 0;
console.log(a2); // false
var a3 = undefined == 0;
console.log(a3); // false
var a1 = null< 0;
console.log(a1); // false
var a2 = null> 0;
console.log(a2); // false
var a3 = null == 0;
console.log(a3); // false
var a3 = null == undefined;
console.log(a3); // true
var a4 = NaN == NaN;
console.log(a3); // false
- < > >= <= 都要经过 number转换的
- undefined 和 null 既不大于0也不小于0 也不等于0 他们和0没有关系
- 但是! undefined和null互相有关系 【考过】
1.5.2 转换为字符串
方式 | 说明 | 案例 |
---|---|---|
toString( ) | 转成字符串 | |
String( ) 强制转换 | 转成字符串 | |
加号拼接字符串 | 和字符串拼接的结果都是字符串 |
numObj.toString([radix]); 也是有基底的
指定要用于数字到字符串的转换的基数(从2到36)。如果未指定 radix 参数,则默认值为 10。
·
【补充】后头有讲到
Object.prototype.toString()
每个对象都有一个 toString() 方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。默认情况下,toString() 方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 “[object type]”,其中 type 是对象的类型。
1.5.3 转换为布尔型
方式 | 说明 |
---|---|
Boolean( ) | 其他类型转成布尔值 |
- 代表 空、否定的值 会被转换为false (‘ ’,0,null,NaN,undefined)
- 其余值都会被转换为true
错误
语法 通用
不同的代码块 错误是不相影响的
console.log(0/0) //NaN
console.log(1/0) //infinity
++,- -问题
var a = 5,
b;
b = a-- + --a;
console.log(b,a); // 8 3
把会先执行的放到前面来,后执行的放到后头。
b = - -a + a- - ; 4 + 4
a再减1 a=3
var a = 5,
b;
b = --a + --a;
console.log(b,a); // 7 3
var a = 5,
b;
b = --a + a++; // 4 + 4
console.log(b,a); // 8 5
比较运算符
- 字符串比较大小
按照字符串相对应的ASCII码,多个字符则从左到右一次对比直到比较出ASCII码的大小为止 - NaN 与 包括自己在内任何东西都不相等
条件表达式
- if-else (范围
- switch-case (定值
注意:
- 互斥性
- 如果多个条件表达式之间是 “互斥”关系,哪个判断和执行语句声明在上面还是下面,无所谓
- 如果多个条件表达式之间有交集的关系,需要根据实际情况,考虑清楚哪个结构声明在上面
- 如果多个条件表达式之间有包含的关系,通常情况下,需要将范围小的声明在范围大的上面。否则,范围小的就没计划执行
逻辑运算
undefined,null,NaN,“ ”,0,false都是假(其他都是真)
var a = 1 && 2;
var b = 1 || 2;
console.log(a,b); // 2 1
var c = 0 || null || 1 || 0; //1
v
与:
遇到真 就往后走,
遇到假或走到最后就返回当前的值
或:
遇到假 就往后走,
遇到真或者走到最后就返回当前的值
for循环
var i = 0;
for(; i < 10;){
console.log(i);
i++;
}
while(i<10){
console.log(i);
i++;
}
for循环不是说固定就要3个式子那么写的。可以提出来写哦!
上面这样写 相当于while循环了
var i = 0;
for(; i;){
console.log(i);
i++;
if(i == 11){
break; // i = 0;
}
}
打印0-100的数
( )里 只能有一句,不能写比较
{ }里 不能出现i++ i- -
var i = 100;
for(;i--;){
console.log(i);
}
作业:
- 用for循环,算出斐波那契数列的第N位
- 背:number parseInt String toString 隐式类型转换 typeof全部背下来
var n = parseInt(window.prompt('算出第几位?'));
var a = 1,
b = 0,
c;
for(var i=2;i<=n;i++){
c = a + b;
b = a;
a = c;
console.log('i='+i+':'+'a=' + a + '----' + 'b=' +b + '----' + 'c=' +c);
}
知新
- Number()强转,会把你转成数字型但不一定就能转成数字。只转纯数字字符串、bool、null
day2
目标
- 掌握函数中arguments的使用方式
- 掌握函数的两种声明方式
- 理解函数的作用域概念
- 掌握全局变量和局部变量的使用方式
- 掌握作用域链的概念
- 掌握预编译的工作原理
- 掌握对象的创建方式
- 掌握对象的属性访问方式
注意:
function test(){
var a = b = 1; // 这个不是 在方法里声明 a b然后赋值为1;而是先声明了b=1,在方法里声明a然后将b赋值给a!
console.log(a,b); // 1 1
}
test();
console.log(a) // 报错
console.log(b) // 1
- 在实参内部传了值的,可以在函数中更改实参的值
- 在实参里边并没有传值,你在函数中给这个形参赋值是没有用的
function test(a,b){
b=3;
console.log(arguments[1]); //undefined
}
test(1);
函数的两种声明方式
1. 命名函数(自定义函数方式)
function fn( ) { …}
2. 匿名函数(函数表达式方式)
var fn = funtion( ) { …}
- 函数表达式方式原理与声明变量方式是一致的
- 函数调用的代码必须写到函数体后面 (结合预编译)
arguments的使用
当我们不确定有多少个参数传递的时候,可以用arguments来获取。arguments对象中储存了传递的所有实参
arguments展示形式是一个伪数组,具有以下特点:
- 有length属性
- 按索引方式储存数据
- 不具有数组的 push,pop等方法
作业:
- 定义一个函数,从wp接收一个饮料的名称,函数返回对应的价格
- 定义一个函数,从wp接收第一个数,接收一个运算符号(+ - * / %),接收第二个数,利用这个函数做运算,并返回运算结果
- 定义一个函数,从wp接受一个n,算出n的阶乘,不能用for循环
- 定义一个函数,从wp接收一个n,算出斐波那契数列的第n位,不能用for循环
day3
- 参数初始化 形参的默认值:undefined
- arguments[ ] 实参
- 谁不是 undefined 我就选谁,要是都是undefined了 那就没办法只能是undefined了
- typeof打印出来的都是字符串
- 递归:
① 找规律
② 出口 (你要跳出来
(队列)等着你结果出来了 才有return出去
预编译——变量、函数提升 ▲
GO、 AO
变量提升: 变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升
函数提升: 函数的声明会被提升到当前作用域的最上面,但是不会调用函数
函数声明整体提升,变量只有声明提升,赋值是不提升
- var a = 1; 是两个步骤
暗示全局变量
函数预编译:在函数要执行之前进行的一个步骤 AO (Action Object 活跃对象/函数上下文)
首先创建AO对象
然后:
- 寻找函数的 形参 和 变量声明 (重复的写一次就可以了,只看你有没有变量声明,有就会放进AO 别的不管都还没执行呗)
- 把 实参值 赋值给 形参
- 寻找函数声明并赋值
- 执行函数
题:
function test(a,b){
console.log(a); //1
c = 0;
var c;
a = 5;
b = 6;
console.log(b); 6
function b(){}
function d(){}
console.log(b); 6
}
test(1)
//AO = {
// a:undefined -->1,
// b:undefined -->function b(){},
// c:undefined,
// d:function d(){}
// }
全局上下文 GO: 在通篇 js执行之前产生了GO 全局上下文 GO === window
- 寻找变量声明
- 寻找函数声明
- 执行
题1:
console.log(a,b); // a(),undefined
function a(){}
var b = function(){}
//GO{
// b:undefined,
// a:function a(){}
// }
题2:
function test(){
var a = b = 1;
console.log(b);
}
test();
//GO{
// b :1
// }
//
//
//看到有test(),所以有AO
//AO{
// a:undefined -->1
// }
自己没有的会到GO里头去找看看有没有 +1
题3:
var b = 1;
function test(){
var a = 1;
var b = 2;
console.log(b); //2
}
test();
函数内部有的话,肯定是找自己的
题4:
var b = 3;
console.log(a); //function a(a)
function a(a){
console.log(a); //function a()
var a = 2;
console.log(a); // 2
function a(){}
var b = 5;
console.log(b); // 5
}
a(1);
//GO{
// b:undefined
// a:function a()
// }
//AO{
// a:undefined -->1 --> function a() -->2,
// b:undefined -->5, //执行的时候会赋值的
// }
执行的时候是会赋值的!
题4:
a = 1;
function test(){
console.log(a); //undefined
a = 2;
console.log(a); // 2
var a = 3;
console.log(a); // 3
}
test();
var a;
//GO = {
// a:undefine ,
// 1
// test:function test(){}
// }
//AO = {
// a:undefined,
// 2,
// 3,
// }
AO里头有a的话,不管是什么值,都不会到GO里头找
题5:
function test(){
console.log(b); //undefined
if(a){
var b = 2;
}
c = 3;
console.log(c); // 3
}
var a;
test();
a = 1;
console.log(a); //1
//GO = {
// a:undefined --,
// test:function test(){}
// c:3
// }
//AO = {
// b:undefined,
// }
只看你有没有变量声明,有就会放进AO
作业:
//------------------ 1
function test(){
return a;
a = 1;
function a(){}
var a = 2;
}
console.log(test()); // function a(){}
// AO{
// a:undefined --> function a(){},
// }
//------------------ 2
function test(){
a = 1;
function a(){}
var a = 2;
return a; //2
}
console.log(test());
//AO{
// a:undefined --> function a(){} -->1 -->2,
// }
//---------------- 3
a = 1;
function test(e){
function e(){}
arguments [0] = 2;
console.log(e); //2
if(a){
var b = 3;
}
var c;
a = 4;
var a;
console.log(b); //undefined
f = 5;
console.log(c); // undefined
console.log(a); //4
}
var a;
test(1)
console.log(a);
console.log(f);
// GO{
// a:undefined,
// test:function test(),
// f:5
// }
//AO{
// e:undefined --> 1 (实参赋值) --> function e() (函数声明赋值) --> 2 (执行时赋值),
// b:undefined,
// c:undefined,
// a:undefined --> 4 (执行赋值),
// }
day2 复习:
var a = false + 1;
console.log(a); // 1
var b = false == 1
console.log(b); // 0 false
//-------------------
if(1 + 5 * '3' ===16){
console.log('通过了');
}else{
console.log('未通过');
}
// 通过了
//-----------------------------------
console.log(!!' ' + !!'' - !!false || '通过了'); // 0+0-0 错 --> 第一个不是空字符串,所以:1+0-0 = 1
// 通过了 错--> 1
//--------------------------------
window.a || (window.a = '1'); // 当前没有a,所以window.a=undefined 所以为假 往后走
console.log(window.a) //1
//错误! 因为后头加了括号,括号的优先级最高,后头的要比前面的先执行,后头的执行完回到前面来执行 遇到真就停止了
//----------------------------
▲
//-------------------感觉这题有点难 没搞清楚
if(typrof(a) && (-true) + (+undefined) + ''){ // 'undefined' && (-1 + NaN + ' ')
// -1 + NaN = NaN -1 = NaN
// NaN + '' = 'NaN'
console.log('ok')
}else{
console.log('no')
}
// ok
未被定义的变量,如果你console.log的话会报错,但是有一种情况,你把它放到typeof里头是不会报错的,typeof会报undefined (而typeof的输出类型是字符串)
day4
作用域、作用域链、闭包、立即执行函数、表达式
1. 作用域 scope、作用域链
函数也是一种对象类型 引用类型 引用值
函数也是具有属性的
function test(a,b){
}
console.log(test.name); // test
console.log(test.length); // 2
作用域 [[ scope ]]:
- 函数创建时,生成的一个JS内部的隐式属性。(只有JS引擎才能读取)
- 存储作用域链的容器
作用域链:
- 用来存放 AO/GO
- 函数执行完成以后,AO是要销毁的。(AO是一个即时的存储容器)
每一个函数 他的作用域链上都有GO
-
当函数被定义的时候,就生成了 [[ scope ]] -> scope chain ->GO
-
当你这个函数在执行前的那一刻会生成AO
例子:
function a(){
function b(){
function c(){
}
c();
}
b();
}
a();
// a定义: a.[[scope]] -> 0 : GO
// a执行: a.[[scope]] -> 0 : a的AO
// 1 : GO
// b定义: b.[[scope]] -> 0 : a的AO
// 1 : GO
// b执行: b.[[scope]] -> 0 : b的AO
// 1 : a的AO
// 2 : GO
// c定义: c.[[scope]] -> 0 : b的AO
// 1 : a的AO
// 2 : GO
// c执行: c.[[scope]] -> 0 : c的AO
// 1 : b的AO
// 2 : a的AO
// 3 : GO
// c执行结束: c.[[scope]] -> 0 : c的AO 删除X
// 1 : b的AO
// 2 : a的AO
// 3 : GO
//回到c定义的状态
// c执行结束: c.[[scope]] -> 0 : b的AO
// 1 : a的AO
// 2 : GO
// b执行结束: b.[[scope]] -> 0 : b的AO 删除X
// 1 : a的AO
// 2 : GO
// 同时 c.[[scope]] X没有了
// b执行结束: b.[[scope]] -> 0 : a的AO
// 1 : GO
// a执行结束: a.[[scope]] -> 0 : a的AO 删除X
// 1 : GO
// 同时 b.[[scope]] X没有了
// a执行结束: a.[[scope]] -> 0 : GO
2. 闭包:
-
简单理解:一个作用域可以访问另外一个函数的局部变量
-
当 内部函数 被返回,外部并保存时,一定会产生闭包,闭包会使原来的作用域链不释放 (它被返回出去的时候 它死死的抓住上一级AO)(因为,内部函数被定义时,是在外部函数环境下,所以内部函数这时的作用域链有外部函数执行期的作用域链)
-
闭包可以延伸变量的作用范围、做一个数据缓存
-
过多的闭包可能会导致内存泄漏,或加载过慢。
// 闭包
function test1(){
var n = 100;
function add(){
n++;
console.log(n)
}
function reduce(){
n--;
console.log(n)
}
return [add,reduce];
}
var arr = test1();
arr[0](); // 101
arr[1](); // 100
//============
function sunSched(){
var opration = {
setSched: function(){
sunSched = thing;
},
show: function(){
console.log("my schedule on sunday is" + sunSched);
}
}
return opration;
}
var sunSched = sunSched();
sunSched.setSched('studing');
sunSched.showSched();
- 想把两个函数返回出去 怎么返回?
用数组/对象 对象怎么返啊!?
循环注册点击事件——闭包笔试常考题
在这里插入代码片
3. 立即执行函数 —— IIFE immediately-invoked function expression (初始化函数)
特点:
- 页面加载的时候它就自动执行了
- 执行完成以后立即释放
立即执行函数是有返回值的,你要接收它的返回值,就把它交给一个变量来保存
写法:
- (function( ){ })( );
- (function( ){ }( )); // W3C建议
- 在函数前面加 + - !|| &&
4. 表达式
- 括号包起来的,不管里头是什么 都变成表达式了
- 一定是表达式才能被执行符号执行
- 表达式会自动忽略你的函数名
那怎么样的才是表达式?
① 常规字面量声明那种
② 被括号包起来了
(function test1(){
console.log(1);
})(); //1
var test2 = function(){
console.log(1);
}(); //1
function test3(){
console.log(1);
}() // 这不是表达式,所以不能执行
函数声明变成表达式的方法: 在函数前面加 + - !|| &&
将函数声明转化为表达式后,就可以使用执行符号,立即执行该函数并且 该函数声明的函数名自动被忽略掉了
面试题:会报错吗?
function test(a,b){
console.log(1);
}(6)
//> 不会,js引擎是这样解析的:
function test(a,b){
console.log(1);
}
(6) //它认为你这个是表达式,而方法没有被调用就不会执行
//你啥都不传的话,它只能认为你是一个立即执行符号
(6) //它认为你这个是表达式,而方法没有被调用就不会执行
//你啥都不传的话,它只能认为你是一个立即执行符号
5.(应该是指在表达式中) 逗号(,) 是个运算符。它只返回所有逗号的最后一个值
面试题:
var fn =(
function test1(){
return 1;
},
function tese2(){
return '2';
}
)();
// fn = (1,'2');
console.log(typeof(fn)); //string
5. 闭包&&立即执行函数经典案例:
funtion test(){
var arr = [];
for(var i = 0; i < 10; i++){
arr[i] = function(){
document.write(i + ' ');
}
}
return arr;
}
var myArr = test();
for(var j = 0;j < 10;j++){
myArr[j]();
}
现在打印出来的全是10 (与闭包有关)
那么怎么写才能打印出0-9
第一种:在你循环的时候就立即执行了
funtion test(){
//var arr = [];
for(var i = 0; i < 10; i++){
(function(){
document.write(i + ' ');
}())
}
}
第二种:借助外界力量
funtion test(){
var arr = [];
for(var i = 0; i < 10; i++){
arr[i] = function(num){
document.write(num + ' ');
}
}
return arr;
}
var myArr = test();
for(var j = 0;j < 10;j++){
myArr[j](j);
}
第三种:还是用立即执行,每一次把这个值给保存下来(这是很实用的一种方法!必会 )
funtion test(){
var arr = [];
for(var i = 0; i < 10; i++){
(function(j){
arr[j] = function(){
document.write(j + ' ');
}
})(i);
}
return arr;
}
var myArr = test();
for(var j = 0;j < 10;j++){
myArr[j]();
}
有点难、有点绕
var a = 10;
if(function b(){}){ // 它不是false 它肯定进去执行 (function b(){})是表达式,所以函数名被忽略了
//函数在这一步的时候已经不存在,b 变成 is not defined
//如果b没有被声明打印出来是会报错的,只有一种情况是放到typeof里面是不会报错的显示的是undefined
a += typeof(b)
}
console.log(a); //10function 错-->10undefined
b为什么是undefined
作业:
- 累加器 闭包 0 执行一次+1
- 一个班级,学生名字保留在一个数组里,两个方法写在函数中的一个对象中,第一个方法写加入班级,第二个方法离开班级每次加入或离开都需要打印新的学生名单
// 作业1
function add(){
var count = 0;
function add1(){
// console.log(count++); //这样写第一次调用的时候打印出来为0
console.log(++count);
}
return add1;
}
var test = add();
test();
day
目标
- 能够使用构造函数创建对象
- 能够说出原型的作用
- 能够说出访问成员的规则
对象
创建对象的方式:
- 对象字面量
- new Object
- 自定义函数
对象的增删改查
怎么删?
var teacher = {
name:'张三',
age:32,
sex:'male',
height:176,
weight:120,
teach:function(){
console.log('i am teaching');
},
smoke:function(){
console.log('i am smoking');
},
eat:function(){
console.log('i am eatching');
}
}
//增
teacher.address = '北京';
//删
delete teacher.address;
delete teacher.teach(); //能成功删除吗? 不能,后边有个执行 (笔试题)
delete teacher.teach
console.log(teacher);
构造函数
- 系统自带的构造函数 与对象字面量相等
var obj = new Object();
obj.name = 'lili';
obj.sex = 'female';
new 在执行时会做四件事情:
- (隐式的)在内存中创建一个新的空对象
- 让 this 指向这个新的对象
- 执行构造函数里面的代码,给这个新对象添加属性和方法
- (隐式的)返回这个新对象
自定义构造函数
- 大驼峰 所有单词首字母大写
- 封装插件、模块化
function Teacher(){
this.name = '张三';
this.sex = '男';
this.smoke = function(){
console.log('i am smoking');
}
}
这个this指代的是谁啊? 构造函数没有执行之前 这个this根本就不存在
你要让this存在必须要实例化它,谁用就是谁
this指向的是实例化的对象
var t = new Teacher(); // t = this
传参:传一个对象进去
function Teacher(opt){
this.name = opt.name;
this.sex = opt.sex;
this.smoke = function(){
console.log('i am smoking');
}
}
解析this指向
作业:
-
写一个构造函数,接受数字类型的参数,参数数量不定,完成参数相加和相乘的功能
-
写一个构造车的函数,可设置车的品牌、颜色、排量
再写一个构造消费者的函数,设置用户的名字,年龄,收入,通过选择的方法实例化该用户喜欢的车,再设置车的属性
// 作业1
function Computate(opt){
this.num = opt.num;
this.add = function(){
var sum = 0;
for(var i = 0; i<this.num.length;i++){
sum += this.num[i];
}
console.log(sum);
}
this.aMultiply = function(){
var accumulate = 1;
for(var i=0; i<this.num.length;i++){
accumulate *= this.num[i];
}
console.log(accumulate);
}
}
var compute = new Computate({
num:[1,2,3,4]
});
compute.add();
compute.aMultiply();
//作业2 --------------------------------------------
function Cars(opt){
this.brand = opt.brand;
this.color = opt.color;
this.displacement = opt.displacement;
console.log('brand:' + this.brand);
console.log('color:' + this.color);
console.log('displacement:' + this.displacement);
}
function Customer(opt){
this.name = opt.name;
this.age = opt.age;
this.income = opt.income;
this.choose = function(){
console.log('age:' + this.age);
console.log('income:' + this.income);
var car = new Cars(opt.cars);
console.log( this.name + '购买了一辆' + car.color + '的' + car.brand + '排量是' + car.displacement);
}
}
var customer1 = new Customer({
name:'lili',
age:21,
income:23425,
cars:{
brand:'奔驰',
color:'pink',
displacement:5
}
})
customer1.choose();
此题参数问题:
在方法里实例化车的构造函数,只需要把参数传到构造函数里就可以了。不用放在choose方法的参数里,这样opt.cars就不会报错
优化for循环性能的一种方式:缓存一下
包装类
- 原始值没有自己的 方法 和 属性
- new Number( )
- new String( )
- new Boolean( )
var a = 1;
console.log(a);
// 数字不一定是原始值
var b = new Number(a);
b.len = 1;
b.add = function(){
console.log(1);
}
console.log(b);
var d = b + 1;
console.log(d) // 2 参与运算时又会返回原始值
// 当数字经过 new Number 了以后,他就会变成数字对象
// 这样就可以设置属性和方法了
console.log(b);
-
对象参与运算的时候会回到原始值
-
undefined 和 null 是不可以设置任何属性和方法的
数组的截断方法
var arr = [1,2,34,5,3];
arr.length = 3;
console.log(arr); // [1,2,34]
为什么string 就可以 .length? —> 通过包装类来访问(或者说字符对象有这个属性
var str = 'abc';
str.length = 1; // 原始值,new String(str).length = 1; (执行后发现没地方保存)
//delete
// new String(str).length 经过包装类来访问
console.log(str.length); //3
题1:
var name = 'liuguiji';
name +=10; // 'liuguiji10'
var type = typeof(name); // 'string'
if(type.length === 6){
type.text = 'string'; // type是原始值,那你是顾客你要求了所以没办法只能给你操作一遍
// new String(type).text = 'string'; 执行完之后发现没有地方储存,所以自删除
}
console.log(type.text); //undefined
偏要你输出type.text 怎么办?加一个包装类
var type =new String(typeof(name));
题2:
function Car(brand,color){
// this = {
// }
this.brand = 'Benz';
this.color = 'red';
}
var car = new Car ('Mazda','blank');
console.log(car); //你没有把参数赋值,所以开始原来写好的值
题3 :
function Test(a,b,c){
var d = 1;
this.a = a;
this.b = b;
this.c = c;
function f(){
d++;
console.log(d); //先d++再打印的d,所以加了1
}
this.g = f;
//return this; --> 闭包
}
var test1 = new Test();
test1.g(); //2
test1.g(); //3
var test2 = new Test();
test2.g(); //2
题4:
var x = 1,
y = z = 0;
function add(n){
return n = n + 1;
}
y = add(x); //2
function add(n){
return n = n + 3;
}
z = add(x); //4
console.log(x,y,z); // 1 2 4
//预编译:
//GO = {
// x:1,
// y:0,
// z:0,
// add:function add(n){return n = n + 3} //覆盖了 所以最后执行的都是这一函数体
//}
//全局上下文 函数提升 变量声明提升
// 同样的名称放到同样的地方去覆盖
错
答案:144
题5:哪个函数可以输出 1 2 3 4 5? (关于立即执行函数
function foo1(x){
console.log(arguments);
return x;
}
foo1(1,2,3,4,5);
function foo2(x){
console.log(arguments);
return x;
}(1,2,3,4,5); //这个函数没有执行,被看成 函数+一个表达式
// function foo2(x){
// console.log(arguments);
// return x;
// }
// (1,2,3,4,5);
(function foo3(x){
console.log(arguments);
return x;
})(1,2,3,4,5);
foo1 和 foo3
arguments 存储传递的所有实参,不管你形参有几个,你实参传了几个都会被存储进去
题:
function b(x,y,a){
a = 10;
console.log(arguments[2]); //10
//另一种
arguments[2] = 10;
console.log(a); //10 映射关系
}
b(1,2,3);
作业:
//ASCII码 表1 0-127 表2 128-255 1个字节 byte
//
//UNICODE码 涵盖ASCII码 256开始2个字节
//
var str = 'a';
var pos = str.charCodeAt(0);
console.log(pos);
写一个函数,接受任意一个字符串算出这个字符串的总字节数
//自己写的:
function byteSum(str){
var item,
sum = 0;
for(var i=0;i<str.length;i++){
item = str.charCodeAt(i);
if(item > 255){
sum +=2;
}else{
sum++;
}
}
console.log(sum);
}
byteSum('abc');
byteSum('小i选哪个');
// 这种方法还是有些冗余
//老师优化版:
//中文占2个字节 只比字母数字多一个字节,所以就再判断多出来的字节
function byteSum(str){
var sum = str.length,
pos;
for(var i=0;i<str.length;i++){
pos = str.charCodeAt(i);
if(pos > 255){
sum++;
}
}
console.log(sum);
}
byteSum('abc');
byteSum('小i选哪个');
讲评在原型与原型链深入、对像继承
day
原型对象 prototype
构造函数方法很好用,但是存在浪费内存的问题
构造函数通过原型分配的函数让所有对象实现共享操作(不用单独开辟内存空间)
- 每一个构造函数里边都有一个原型对象(console.dir(构造函数名称) )可以查看到
-
是function对象的一个属性
打印出来看了一下,结果它(原型)也是对象,所以称为原型对象 -
这个prototype是定义构造函数构造出的每个对象的公共祖先
-
所有被该构造函数构造的对象都可以继承原型的属性和方法
-
(实例化对象)自己有的属性就不会到原型上去找属性了
-
构造出来的对象对原型的增删改查问题——只能查
作用:
我们在实例化对象的时候总有一些写死的值
这些写死的值,在每次new的时候我们都需要去走一遍这个流程,这种代码对于实例化来说是一种代码的冗余,也算是一种耦合(重复了)在这种情况下,我们得想一个办法 能不能让它继承谁,在这种时候,我们把要写死的内容挂到原型上去
当我们需要用参数去传值的这些, 我们就写到this里面去,当我们需要写死的这些,我们就写到原型上去直接继承就可以了
经验:
一个插件,方法往往被写到原型上去。部分属性写到构造函数内部。因为属性往往都是配置项 需要传参去配置的,而方法都是一样的
function Handphone(brand,color){
this.color = color;
this.brand = brand;
this.screen = '16:9';
}
// Handphone.prototype.rom = '64G';
// Handphone.prototype.ram = '6G';
// Handphone.prototype.screen = '18:9';
// Handphone.prototype.system = 'Android';
// Handphone.prototype.call = function(){
// console.log('i am calling');
// }
//
// 优化代码:
Handphone.prototype = {
rom:'64G',
ram:'6G',
screen:'18:9',
system:'Android',
call:function(){
console.log('i am calling');
},
}
var hp1 = new Handphone('小米','red');
var hp2 = new Handphone('华为','black');
console.log(hp1.rom);
console.log(hp2.ram);
console.log(hp1.screen); // 16:9 自己有的属性就不会到原型上去找属性了
console.log(hp2.screen); // 16:9 自己有的属性就不会到原型上去找属性了
hp2.call();
对象原型 __proto __
对象都会有一个属性__proto __ 指向构造函数的prototype 原型对象 (我们对象 可以使用构造函数prototype 原型对象的属性和方法,是因为对象有__proto __原型的存在)
- _proto_是实例化以后的结果,属于对象实例的
每个实例化对象原型的容器,它就是装prototype的
proto也是能改的(更改装的内容)
function Car(){
// new实例化以后,隐式的var了一个this的一个空对象
// var this = {
__proto__:Car.prototype
// }
//
}
Car.prototype.name = 'Benz';
var car = new Car();
console.log(car);
▲
-
原型上面的构造器默认是指向构造函数本身的
可以通过更改原型里的构造器,硬给它指向别的 -
如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,他会把原来的constructor覆盖掉,则必须手动的利用constructor指回原来的构造函数
-
constructor的作用:告诉我们你这个对象是通过哪个构造函数创建出来的
Handphone.prototype = {
constructor:Telephone,
}
var p1 = {
name:'里斯'
}
car.__proto__ = p1;
- 原型一定是属于实例化对象的而不是构造函数的
function Car(){}
Car.prototype.name = 'Mazda';
var car = new Car();
Car.prototype.name = 'Benz';
console.log(car.name); //Benz
//-------------
Car.prototype.name = 'Mazda';
function Car(){}
var car = new Car();
Car.prototype.name = 'Benz';
console.log(car.name); //Benz
//--------------
Car.prototype.name = 'Mazda';
function Car(){}
Car.prototype.name = 'Benz';
var car = new Car();
console.log(car.name); //Benz
以上都是对属性的重写
Car.prototype.name = 'Benz';
function Car(){}
var car = new Car();
Car.prototype = {
name:'Mazda'
}
console.log(car.name); //Benz
解析
// fucntion Car(){
// this{
// __proto__:Car.prototype {
// name:'Benz'
// }
// }
// }
//
// Car.prototype.cunstructor -> Car() -> prototype -> name:'Mazda'
属于给prototype的name赋值,另一个属于重写prototype
window return
//
function test(){
var a = 1;
function plus1(){
a++;
console.log(a);
}
return plus1; //把plus送到全局去了,变成了全局函数
}
var plus = test(); //用plus将它保存 需要一个全局变量去保存它
plus();
plus();
plus();
function test(){
var a = 1;
function add(){
a++;
console.log(a);
}
window.add = add;
}
test();
add(); //2
add(); //3
add(); //4
var add = (function(){
var a = 1;
function add(){
a++;
console.log(a);
}
return add; // 或 window.add = add;
})();
add(); //2
add(); //3
add(); //4
- 自启动函数
- 写立即执行函数,首先打一个分号 这是一种习惯 怕忘了打
js插件的写法:
立即执行函数里面写一个构造函数
将这个构造函数保存到window上的一个变量
- 一个自启动函数,插件的标配
(function(){
function Test(){}
window.Test = Test;
})();
var test = new Test();
作业:写一个插件
任意传两个数字,调用插件内部方法可进行加减乘除功能
prototype中 怎么拿到参数、构造函数的属性?
答:
在实例化对象后,你调用方法,发现构造函数里头没有就会到原型里头去寻找。 谁使用this就指向谁
// ;(function(){
// function Compute(opt){
// this.a = opt.a;
// this.b = opt.b;
// this.add = function(){
// var sum = 0;
// sum = this.a + this.b;
// console.log(sum);
// };
// this.reduce = function(){
// var red = 0;
// red = this.a - this.b;
// console.log(red);
// };
// this.mul = function(){
// var acc = 1;
// acc = this.a * this.b;
// console.log(acc);
// };
// this.divide = function(){
// var shang;
// shang = this.a / this.b;
// console.log(shang);
// }
// }
// window.Compute = Compute;
// })();
//
//
;(function(){
function Compute(opt){
this.a = opt.a;
this.b = opt.b;
}
Compute.prototype = {
add:function(){
var sum = 0;
sum = this.a + this.b;
console.log(sum);
},
reduce:function(){
var red = 0;
red = this.a - this.b;
console.log(red);
},
mul:function(){
var acc = 1;
acc = this.a * this.b;
console.log(acc);
},
divide:function(){
var shang;
shang = this.a / this.b;
console.log(shang);
}
}
window.Compute = Compute;
})();
var compute = new Compute({
a:20,
b:4
});
compute.add();
compute.reduce();
compute.mul();
compute.divide();
作业笔记:
参数可以在调用方法的时候传递,所以可不写在构造函数上,写在方法上
原型链
-
原型链的顶端 Object.prototype
object仍然是有原型的 -
Object.prototype底下保存了一个 toString的方法
-
原型链上的增删改只能是它自己本身(不是绝对的)
原型深入
题1:
//面试题:
Professor.prototype.tSkill = 'JAVA';
function Professor(){}
var professor = new Professor();
Teacher.prototype = professor;
function Teacher(){
this.mSkill = 'JS/JQ';
this.success = {
alibaba:'28',
tencent:'30'
}
}
var teacher = new Teacher();
Student.prototype = teacher;
function Student(){
this.pSkill = 'HTML/CSS';
}
var student = new Student();
//这样更改他的父级元素(就是他老师的属性)行不行
//再验证一下这条语句会不会在student里面加一个新的success属性,然后这个属性对应一个对象,teacher里头的会不会被复制过来,且里头写上百度
student.success.baidu = '100';
console.log(teacher,student);
//这两个打印有没有变化?
//teacher的加上了
//他没有赋值到student实例里边,他赋值到实例的原型里边(这是因为你teacher变了,你把teacher赋值到Student.prototype,所以他在实例的对象原型里)
//学生可以修改teacher的引用值属性
题2:
//面试题:
Professor.prototype.tSkill = 'JAVA';
function Professor(){}
var professor = new Professor();
Teacher.prototype = professor;
function Teacher(){
this.mSkill = 'JS/JQ';
this.students = 500;
}
var teacher = new Teacher();
Student.prototype = teacher;
function Student(){
this.pSkill = 'HTML/CSS';
}
var student = new Student();
student.students++;
console.log(teacher,student);
//这两个打印有没有变化?
//老师不变,学生的增加了一个students属性值为501
//学生不能去修改teacher的原始值属性,但是它会将teacher。students复制下来,他认为你想给student增加一个students属性
// student.students = student.students + 1 (student.students取值,因为往上找能找到为500 所以是500+1)
题3:
//笔试题
//
//
function Car(){
this.name = 'Benz';
}
Car.prototype = {
brand:'Mazda',
intro:function(){
console.log('我是' + this.brand + '车');
}
}
var car = new Car();
//new了:
//function Car(){
// var this = {
// brand:'Benz'
// 这个时候要调用intro方法,但是我对象里头没有。所以就到原型里面去找
// }
// Car.ptototype--> intro() 里的this指向对象本身,刚好我对象里头有brand 所以打印出Benz
//
//}
car.intro(); //Benz
谁在使用this,this就指向谁
那么想让打印出来的是Mazda得怎么写?
Car.prototype.intro();
题3:
- 普通函数不写返回值,默认返回undefined return undefined;
- 构造函数通过实例化了以后,返回的是this
function Person(){
this.smoke = function(){
this.weight--;
}
}
Person.prototype = {
weight:130
}
var person = new Person();
person.somke();
// this.weight = this.weight - 1;
// 取值,原型里有
// 130 -1
// 而person里没这个属性
// person添加weight属性
console.log(person);
创建对象(4种):
用字面量和系统自带的构造器去声明的对象,他们的构造器都是Object,他们两的原型都是原型链顶端的 Object.prototype
var obj1 = {};
console.log(obj1);
var obj2 = new Object(); //公司一般不用这个,因为它和字面量声明没有区别,添加属性什么的又麻烦、乱
console.log(obj2);
function Obj(){}
var obj3 = new Obj();
console.log(obj3); //它的构造器指向的是Obj()自定义的构造器
-
除了写插件,尽量都用字面量去声明对象
-
原型的原型一定是Object构造出来的
构造函数
Object.create(对象/null) //必须得放内容,二选一
- 功能:你可以指定对象原型,(里面放你想要的原型)
function Obj(){}
Obj.prototype.num = 1;
var obj1 = Object.create(Obj.prototype);
var obj2 = new Obj();
console.log(obj1);
console.log(obj2);
他们两个其实是一样的,对象原型、构造函数都是一样的
new的工作:
- 实例化obj2
- 调用构造函数Obj的初始化属性和方法
- 指定实例对象的原型 proto:Obj.prototype
//创建obj1空对象
var obj1 = Object.create(null); //完全空的,对象原型都没了__proto__
是不是所有的对象都应该继承 Object.prototype 呢?
不是,Object.create(null)这种对象就不会继承
undefined null 能不能使用toString()?
不能,undefined和null不能经过包装类,且他们没有原型
var num = 1;
num.toString(); //1
这个可以是因为它经过了包装类
那为什么Number要有一个自己的toString(),而不继承Object.prototype里面的toString()
// 方法的重写,因为Object的不满足需求
var num = 1;
var obj = {};
var obj3 = Object.create(null);
document.write(num);
document.write(obj);
document.write(obj3); //报错
//document.write()打印的时候有一个隐式转换,转换成string
call、apply 更改this的指向
案例:
function Compute(){
this.plus = function(a,b){
console.log(a + b);
}
this.minus = function(a,b){
console.log(a-b);
}
}
function FullCompute(){
Compute.apply(this);
this.mul = function(){
console.log(a * b);
}
this.div = function(){
console.log(a / b);
}
}
var compute = new FullCompute();
compute.plus(1,2);
compute.minus(1,2);
compute.mul(1,2);
compute.div(1,2);
1. 函数内的this指向
函数的 不同调用方式 决定了 this的指向不同
- 普通函数——this 指向window
- 对象的方法——this 指向的是对象
- 构造函数——this 指向实例对象
(原型对象里面的this 也指向这个实例对象) - 绑定事件函数——this 指向这个函数的调用者
- 定时器函数——this 指向的是window
- 立即执行函数——this 指向的是window
2. 改变函数内this指向:
call( )、 apply( )、bind( ) 三种方法
1. call方法
- 可以调用函数
- 可以改变函数的this指向
- 主要应用:实现继承
fun.call(thisArg,arg1,arg2,...)
2. apply方法
- 可以调用函数
- 可以改变函数的this指向
- 注意:它的参数必须是数组(伪数组)
- 主要应用:例如我们可以利用apply借助于数学内置对象求数组最大值
//apply 应用的意识
fun.apply(thisArg,[argsArray])
- thisArg:在fun函数运行时指定的this值
- argsArray:传递的值,必须包含在数组里面
- 返回值就是函数的返回值,因为他就是调用函数 ???
3. bind方法
- 不会调用函数
- 可以改变函数内部this指向
- 返回的是 原函数改变this指向之后产生的新函数
- 主要应用:如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向,此时,使用bind
// bind() 绑定 捆绑的意思
fun.bind(thisArg,arg1,arg2,...);
- thisArg:在fun函数运行时指定的this值
- arg1,arg2:传递的其他参数
- 返回由指定的this 值和初始化参数改造的原函数拷贝
作业:
年龄为多少岁姓名为XX买了一辆排量为XX的什么颜色的什么牌子的车 call apply
function Cars(opt){
this.brand = opt.brand;
this.color = opt.color;
this.displacement = opt.displacement;
}
function Customer(opt){
Cars.call(this,opt);
this.name = opt.name;
this.age = opt.age;
this.income = opt.income;
this. resultInfo = function(){
console.log(this.age + '的' + this.name + '购买了一辆' + this.color + '的' + this.brand + '排量是' + this.displacement);
}
}
var customer1 = new Customer({
name:'lili',
age:21,
income:23425,
brand:'奔驰',
color:'pink',
displacement:5
})
customer1.resultInfo();