值类型
- 原始值(stack)
- number string boolean null undefined
- 引用值(heap)
- object array function ...
- object array function ...
var num = 100;
num= 200;
var a = 10;
var b = a;
a = 20;
console.log(b)
var b = a; 过程为a先取出来10拷贝一份放在b里面,不管是改a里面的10还是b里面的10,和另外一个都没有关系。栈内存和栈内存之间的赋值是拷贝关系。
var arr = [1,2];
var arr1 = arr;
console.log(arr1)
var arr = [1];
var arr1 = arr;
arr = [1,3];
console.log(arr1)
arr = [1,3] 会从堆内存重新申请一个房间
function deepClone(origin, target){
var target = target || {};
toStr = Object.prototype.toString;
arrStr = "[object, Array]";
for(var prop in origin){
if(origin.hasOwnProperty(prop)){
if((origin[prop]) !== "null" && typeof(origin[prop]) == 'object'){
if(toStr.call(origin[prop]) == arrStr) {
target[prop] = [];
} else {
target[prop] = {};
}
deepClone(origin[prop],target[prop])
} else {
target[prop] = origin [prop]
}
}
}
return target;
}
var a = {
name: 123,
wife: {
name: 'ceshi'
}
};
var b = {};
deepClone(a, b)
运算符
- 比较运算符
- '>' '<' '==' '>=' '<=' '!='
- 比较结果为boolean值
- 逻辑运算符
- '&&' 先看第一个表达式转换为布尔值的结果 如果为真,那么它会看第二个表达式转换为布尔值的结果, 如果为假,直接返回第一个表达式转换为布尔值的结果。(全真为真,一假为假)
- '||' 先看第一个表达式转换为布尔值的结果 如果为真,那么它会看第一个表达式转换为布尔值的结果, 如果为假,返回第二个表达式转换为布尔值的结果。(一真为真,全假为假)
- '!' 转换为布尔值 然后取反
- 被认定为false的值
- undefined null NaN '' 0 false
循环语句
- for循环
- while (满足条件执行)
- do while(无论条件是否满足,首先要执行一次)
- switch case
var n = 'a';
switch(n) {
case "a":
console.log('a');
break;
case "b":
console.log('b');
break;
case "c":
console.log('c');
break;
}
- break 终止循环 一定要放在循环里面
- continue(终止本次循环,执行下一次循环)
// 如果满足整除7,则不打印。
for(var i = 0; i < 100; i++){
if(i % 7 === 0){
continue;
}
console.log(i)
}
typeof 类型转换
- number
- string
- boolean
- object
- undefined
- function
var num = NaN;
typeof(num) "number"
var num = [];
typeof(num) "object"
var num = null;
typeof(num) "object"
var num = undefined;
typeof(num) "undefined"
var num = function(){};
typeof(num) "function"
显示类型转换
- Number()
- parseInt(string, radix)
- parseFloat()
- toString()
- Boolean()
- String()
var num = Number('123'); 123
var num = Number(false); 0
var num = Number(undefined) NaN
var num = Number('abc') NaN
var num = Number('123abc') NaN
var num = parseInt(true); NaN
var num = parseInt(false); NaN
var num = parseInt(undefined) NaN
var num = parseInt('abc') NaN
var num = parseInt('123.9') 123
var num = parseInt('123abc') 123
var num = undefined;
num.toString(); // 报错
var num = null;
num.toString(); // 报错
隐式类型转换
- isNaN()
- ++ / -- (加加 或 减减) +/-(正负)
- +(加号)
- -(减号)*(乘号)/(除号)%(摩尔)
- && || !
-
< = >= <=
- == !=
isNaN('abc')
// 实际过程为:
Number('abc') 然后 和 NaN 对比
isNaN(null)
Number('null') 0 ===> NaN false
isNaN(undefined)
Number('undefined') NaN ===> true
// 正负号隐式类型转换调用的是Number()
var a = +'abc'
console.log(typeof(a)) number
console.log(a) NaN
加号隐式类型转换调用的是String();
当加号两侧有一个是字符串类型,会调用String(),全部转换为字符串类型。
-(减号)*(乘号)/(除号)%(摩尔) 隐式类型转换调用的是Number()
NaN == NaN false
undefined == null true
注意:返回值为字符串
typeof(a) 'undefined'
typeof(typeof(a)) 'string'
函数
定义函数
- 函数声明
- 函数表达式
// 函数声明
function theFirstName(){
}
// 命名函数表达式
var test = function abc() {
document.write('a');
}
// 匿名函数表达式 (简称函数表达式)
var demo = function () {
document.write('b');
}
arguments
在每一个函数里面都有一个arguments叫做实参列表.为数组。
function sum(a, b) {
//arguments [1, 2]
a = 2;
console.log(arguments[0]) 2
}
sum(1,2);
function sum(a, b) {
//arguments [1, 2]
a = 2;
arguments[0] = 3;
console.log(a); 3
}
sum(1,2);
函数内部有一条映射规则,形参改变,arguments也会跟着改变。arguments改变,形参也会改变。
function sum(a, b) {
a = 2;
console.log(arguments[0]) 2
}
sum(1);
// 下面这种情况不会发生映射
function sum(a, b) {
b = 2;
console.log(arguments[1]) undefined
}
sum(1);
练习 递归实现n的阶乘
function mul(n) {
if(n === 1 || n === 0){
return 1;
}
return n * mul(n - 1)
}
mul(5);
作用域
- 定义: 变量和函数生效的区域。
预编译
js运行过程
- 语法分析
- 预编译
- 解释执行(解释一行,执行一行)
// 函数声明 整体提升
test();
function test() {
console.log('a');
}
// 变量 声明提升
console.log(a); undefined
var a = 123;
预编译前奏
- imply global 暗示全局变量:即任何变量,如果未经声明就赋值,此变量就为全局对象所有。
eg: a = 123;
eg: var a = b = 123;
- 一切声明的全局变量,全是window的属性。
eg: var a = 123; ===> window.a = 123
预编译
- 第一步 创建A0对象 Activation Object(执行期上下文)
- 第二步 找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
- 第三步 将实参值和形参统一
- 第四步 在函数体里面找函数声明,使赋予函数体
function fn(a){
console.log(a); function a (){}
var a = 123;
console.log(a); 123
function a() {}
console.log(a); 123
var b = function() {}
console.log(b); function (){}
function d(){}
}
fn(1);
作用域、作用域链
[[scope]]:每个jsvascript函数都是一个对象,对象中有些属性能访问,但有些不可以,这些属性仅供javascrip引擎读取,[[scope]]就是其中一个。[[scope]]指的就是我们所说的作用域,其中存储了运行期的上下文。
作用域链: [scope]中所存贮的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。
运行期上下文: 当函数执行时,会创建一个执行上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行期上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕后,它所产生的执行上下文会被销毁。
查找变量:从作用域的顶端依次向下查找。
function a () {
function b(){
var b = 234;
}
var a = 123;
b();
}
var glob = 100;
a();
闭包
当内部函数被保存到外部时,将生成闭包。闭包会导致原有作用域链不释放,造成内存泄漏。
闭包作用
- 实现公有变量
- 可以做缓存()
- 可以实现封装,属性私有化
- 模块化开发,防止污染全局变量
// 实现封装 属性私有化
function Test(){
var name = '张小慧';
this.happy = function () {
console.log('我的名字叫',name)
}
}
var test = new Test();
test.happy();
test.name // undefiend
// 模块化开发,防止污染全局变量
var name = 'abc';
var init = (function() {
var name = 'bcd';
function callName() {
console.log(name);
}
return function (){
callName();
}
}())
init(); // bcd name变量私有化
构造函数Test中happy()方法为什么可以访问name变量?var name = '张小慧';是执行上下文中的变量,用完会被销毁。this.happy()被保存到了外部。生成了闭包。this.happy()函数有Test函数的上下文。 test.name 最后打印出来是 undefined name对于Test函数来说是私有变量。
立即执行函数
此函数没有声明,在一次执行过后即释放。适合做初始化工作。
var num = (function (a, b, c){
var d = a + b + c;
return d;
}(1, 2, 3))
console.log(num)
// 只有表达式才能被执行符号执行
// 函数声明
function test(){
}() // 报错 只有表达式才能被执行
// 能被执行符号执行
var test = function() {
console.log('a')
}()
console.log(test) // undefuined
对象
对象创建方法
- var obj = {} plainObject 对象字面量/对象直接量
- 构造函数创建方法
- 系统自带的构造函数 var obj = new Object()
- 自定义构造函数(大驼峰命名规则)
function Car(color){
this.color = color;
this.name = 'BMW';
this.height = '1400';
this.lang = '4900';
this.weight = 1000;
}
var car = new Car('red');
var car1 = new Car('green');
function Car(color){
this.color = color;
this.name = 'BMW';
this.height = '1400';
this.lang = '4900';
this.weight = 1000;
return {}; // 返回{}
return 123; //忽略
}
var car = new Car('red');
var car1 = new Car('green');
构造函数内部原理
- 在函数体最前面隐式加上this = {}
- 执行this.xxx = xxx
- 隐式的返回this
function Person(name, height){
var that = {};
that.name = name;
that.height = height;
return that;
}
var person = Person('zhangxiaohui', 165)
包装类
原型
- 定义: 原型是对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。
- 利用原型的特点和概念,可以提取公共属性。
- 对象如何查看原型 ---> 隐式属性 proto
- 对象如何查看对象的构造函数 -->constructor
Person.prototype.name = 'abc';
function Person() {
// var this = {
__proto__: Person.prototype
}
}
var obj = {
name: 'sunny';
}
var person = new Person();
person.__proto = obj;
obj.name = 'sunny';
person.name 会先查找构造函数里面有没有name,如果this.__proto__没有,向Person.prototype里面查找
call /apply
- 作用,改变this指向
- 传参不同
function test(){}
test() ===> test.call()
function Person(name, age) {
this.name = name;
this.age = age;
}
var obj = {}
Person.call(obj, 'cheng', 300);
console.log(obj) {name: "cheng", age: 300}
>借用别人方法实现自己功能
function Person(name, age, sex){
this.name = name;
this.age = age;
this.sex = sex;
}
function Student(name, age, sex, tel, grade) {
Person.call(this, name, age, sex);
this.tel = tel;
this.grade = grade;
}
var stuent = new Student('sunny', 123, 'male', 139, 2018);
console.log(stuent);
继承
- 传统形式 原型链
- (缺点)过多了继承了没用的属性
- 借用构造函数
- (缺点)不能继承借用构造函数的原型
- (缺点)每次多调用了一个构造函数
- 共享原型
- 不能随便改动自己的原型
- 圣杯模式
Father.prototype.lastName = "Deng";
function Father() {
}
function Son(){
}
function inherit(Target, Origin) {
Target.prototype = Origin.prototype;
}
inherit(Son,Father);
var son = new Son();
console.log(son.lastName);
function inherit(Targrt, Origin){
function F() {
}
F.prototype = Origin.prototype;
Targrt.prototype = new F();
Targrt.prototype.constuctor = Targrt;
Targrt.prototype.uber = Origin.prototype;
}
Father.prototype.lastName = 'Deng'
function Father(){
}
function Son() {
}
inherit(Son, Father);
var son = new Son();
var father = new Father();
Son.prototype.name = 'huihui';
console.log(son.name) huihui
console.log(father.name) undefined
命名空间
- 管理变量,防止污染全局,适用于模块化开发。
var org = {
department1: {
zhangxiaohui: '',
lushasha: '',
}
department2: {
nala: '',
panpan: '',
}
}
var zhangxiaohui = org.department1.zhangxiaohui;
对象枚举
- for in
var obj = {
name: '123',
age: 123,
sex: 'male'
}
for(var prop in obj){
console.log(obj.prop) undefined * 3
因为 这里 obj.propv 会转换成 obj['prop']
}
var obj = {
name: '123',
age: 123,
sex: 'male',
__proto__: {
lastName: 'hui',
}
}
for(var prop in obj){
console.log(obj[prop]) // 返回原型上的东西 不会返回系统原型上的东西
}
- hasOwnProperty
var obj = {
name: '123',
age: 123,
sex: 'male',
__proto__: {
lastName: 'hui'
}
}
for(var prop in obj){
if(obj.hasOwnProperty){
console.log(prop) //不会返回原型链上的东西
}
}
- in
var obj = {
name: '123',
age: 123,
sex: 'male',
__proto__: {
lastName: 'hui'
}
}
console.log('name' in obj) true
console.log('lastName' in obj) true
- instanceof
// 看A对象的原型链上有没有B的原型
A instanceof B
function Person(){
}
var person = new Person();
console.log(person instanceof Person) // true
[] instanceof Array // true
[] instanceof Object //true
判断值为数组还是对象
// 第一种方法
var array = [];
var obj = {};
console.log(array.constructor); //ƒ Array() { [native code] }
console.log(obj.constructor) //ƒ Object() { [native code] }
// 第二种方法
console.log(array instanceof Array) // true
console.log(obj instanceof Array) // false
// 第三种方法
console.log(Object.prototype.toString.call(array)); //[object Array]
console.log(Object.prototype.toString.call(obj)); //[object Object]
this
- 函数预编译过程this -> window
- 全局作用域 this->windoow
- call/apply可以改变函数运行时的this指向
- obj.function(){} (function 里面的this指向obj)
var name = '222';
var a = {
name: '111';
say: function() {
console.log(this.name);
}
}
var fun = a.say;
fun(); // 全局调用 222
a.say(); // 111
var b = {
name: '333';
say: function(fun){
// this ---> b 注意这里不是 this.b
fun(); // 没有人调用 执行方式预编译
// 相当于输出 a 里面的console.log(this.name)
}
}
b.say(a.say); //222
b.say = a.say;
b.say(); // 333
var foo = 123;
function print() {
this.foo = 234;
console.log(foo);
}
print(). // 234
console.log(foo) print AO里面没有foo,打印的是GO里面的foo.this指向window.
var foo = 123;
function print() {
this.foo = 234;
console.log(foo);
}
new print(); // 123
new print 会在 print函数里面 隐式发生 var this = Object.create(print.prototype) this 指向 print