视频地址:https://www.bilibili.com/video/av24383268/?p=13
if(!("a" in window)){ // !true
var a = 1;
}
console.log(a);
输出:undefined
不管条件是否成立都要进行变量提升,在全局作用域下声明的变量,也相当于给window设置了一个对象的属性,而且两者之间建立了映射机制 <=> window.a = undefined
in
:检测某一个属性是否隶属于这个对象,不管是私有属性还是公有属性,只要有这个属性结果就是true
hasOwnProperty
:检测某一个属性是否为对象的私有属性(只有这个属性是私有的才可以)
var a = 4;
function b(x, y, a){
console.log(a);
arguments[2] = 10;
console.log(a);
}
a = b(1,2,3); // a = undefined
console.log(a)
输出:3 10 undefined
arguments
:函数内置的实参集合,不管是否设置形参,传递的实参值都在这个集合中,结构如下:
arguments: {
0: 1,
1: 2,
2: 3,
length: 3,
callee: // 函数本身
}
在非严格模式下,函数中的形参变量存在映射机制(相互之间存在影响),改变形参arguments
的值会改变,改变arguments
形参中对应的参数会改变
function fn(x, y){
var arg = arguments;
arg[0] = 2;
console.log(x); // => 2
y = 1;
console.log(arg[1]) // => undefined
}
fn(10)
ARG和形参之间的映射是以ARG的索引为基础完成的,ARG中有这个索引,浏览器会完成和对应形参变量中的映射机制搭建,如果形参比ARG中的个数多,那么多出来的形成那是无法和ARG中的索引建立关联的。
arguments的映射机制,是依赖于索引而进行的
function fn(x, y){
var arg = arguments;
arg[0] = 2;
console.log(x); // => 2
arg[1] = 1;
console.log(y) // => undefined
y = 400;
console.log(arg[1]);
}
fn(10)
arguments和形参的映射机制是建立在函数执行后形参赋值的一瞬,此时能建立映射机制的建立映射机制,不能建立起来的,以后不管怎么操作都无法建立了
在当前作用域的第一行添加 "use strict"
即可,这样在当前作用域中就开启了严格模式
arguments.callee / arguments.callee.caller
arguments
和形参没有映射机制.
),不再像严格模式下,统一交给 window
,而是让 this
指向 undefined
var foo = 'hello';
(function(foo){
/*
形参赋值:foo = 'hello'
变量提升:var foo; 这一步省略:因为在私有作用于中已经有foo这个变量了,浏览器不会重新声明重复的变量
*/
console.log(foo);
var foo = foo || 'world';
console.log(foo) // => 'hello'
})(foo); // 把全局下的foo的值作为实参传递给函数的形参 => "hello"
console.log(foo); // => 'undefined'
结果: ‘hello’ ‘undefined’
或赋值
var a = 1 || 2;
首先验证1是真假,如果为真,把1赋值给a,如果为假,把2赋值给a
与赋值
var b = 1 && 2;
先验证A的真假,为真结果是B,为假结果是A
EXP1. 逻辑或:
function fn(x){
x = x || 0; // 如果x没传值,将x赋值为0 (这种写法不严谨,如果传入false就无法判断)
}
fn()
EXP2. 逻辑与
function fn(callback){
callback && callback();
// 如果传入了一个callback就执行callback(不严谨,如果传入的不是方法就会导致代码报错)
}
fn(function(){
// ... ...
})
优先级:逻辑与的优先级高于逻辑或
0 || 1 && 2 || 0 || 3 && 2 || 1
// 1 && 2 => 2
// 3 && 2 => 2
// 0 || 2 || 0 || 2 || 1
// 2 || 0 || 2 || 1
// 0 || 2 || 1
// 2 || 1
// 2
var a = 9;
function fn(){
a = 0;
return function(b){
return b+a++;
}
}
var f = fn();
console.log(f(5)); // 5
console.log(fn()(5)); // 5
console.log(f(5)); // 6
console.log(a) // 2
var arr = [1,2,3,4] // arr = bbbfff111 => [1,2,3,4]
function fn(arr){
arr[0] = 0;
arr = [0], // arr = bbbfff222
arr[0] = 100;
return arr;
}
var res = fn(arr); // res = fn(bbbfff111) = bbbfff222
console.log(arr); // => [0,1,2,3]
console.log(res); // => [100]
输出:[0,1,2,3] [100]
function fn(i){
return function(n){
console.log(n + (i++));
}
}
var f = fn(10); // i=10
f(20); // 30 i++ => 11
fn(20)(40); // 60 没有人存放fn(20),形成的作用域自动销毁
fn(30)(50); // 80 同上
f(30); // 41
结果:30 60 80 41
var num = 10
var obj = {num: 20};
obj.fn = (function(num){ // num = 20
this.num = num * 3; // this.num = window.num = num*3 = 60 (自执行函数在非严格模式下执行this指向为window)
num++; // obj.fn.num = 21
return function(n){
this.num += n; // this指向调用者,如果没有调用者则指向window
num++;
console.log(num);
}
})(obj.num)
// window.num = 60
// obj.fn.num = 21
var fn = obj.fn;
fn(5); // n = 5; this.num = window.num + n = 65; num++ = obj.fn.num + 1 = 22 => num = obj.fn.num = 22
// window.num = 65
// obj.fn.num = 22
// obj.num = 20
obj.fn(10); // n = 10; this.num + 10 = onj.num + 10 = 30; num++ = obj.fn.num+1 = 23 => num = obj.fn.num = 23
// window.num = 65
// obj.num = 30
console.log(num, obj.num);
结果:22 23 65 30
var num = 10,
obj = { num: 20 }
obj.fn = (function (num) {
num = this.num + 10;
this.num = num + 10;
return function (){
this.num += ++num;
}
})(num);
// obj.fn.num = 10;
// obj.fn.num = this.num + 10 = window.num + 10 = 20;
// this.num = window.num = obj.fn.num + 10 = 30;
// => window.num = 30; obj.fn.num = 20
// => return 000fffaaa
var fn = obj.fn;
// fn = 000fffaaa;
fn();
// num + 1 = obj.fn.num + 1 = 21
// => obj.fn.num = 21
// this.num = window.num = window.num + obj.fn.num = 51
// => window.num = 51
obj.fn();
// num + 1 = obj.fn.num + 1 = 22;
// => obj.fn.num = 22;
// this.num = obj.num = obj.num + 22 = 42;
// => obj.num = 42
console.log(num, obj.num)
// 51 42
function Fn(){
this.x = 100;
this.y = 200;
this.getX = function(){
console.log(this.x)
}
}
Fn.prototype.getX = function(){
console.log(this.x);
};
Fn.prototype.getY = function(){
console.log(this.y);
}
var f1 = new Fn;
var f2 = new Fn;
console.log(f1.getX === f2.getX); // false
console.log(f1.getY === f2.getY); // true
console.log(f1.__proto__.getY === Fn.prototype.getY); // true
console.log(f1.__proto__.getX === f2.getX); // false
console.log(f1.getX === Fn.prototype.getX); // false
console.log(f1.constructor); // {x: 100, y: 200, getX: [Function]}
console.log(Fn.prototype.__proto__.constructor); // null
f1.getX(); // 100
f1.__proto__.getX(); // null
f2.getY(); // 200
Fn.prototype.getY(); // null
1
与 new Number(1)
Fn()
与 new Fn()
prototype下的name属性指函数名,length属性指传入的形参的个数
var people = function () {
this.age = "18";
}
people.prototype.age = "20"
console.log(people.age); // undefined
console.log(people.prototype.age); //20
var a = new people();
console.log(a.age); // 18
console.log(a.__proto__.age); // 20
创建一个函数(类)后,生成的是一个堆内存,堆内存里存放的是一个字符串。
people
下只有两个属性,一个是constructor
指向函数(类)本身,一个是prototype
指向将来可执行的方法和属性,函数(类)本身也可以通过访问prototype
来访问或执行这些方法。
通过this.xx
追加的属性,并非是添加到prototype
上,而是供实例化后,this
把属性追加到实例化的对象上。
原型链的查找机制是针对于实例化的对象的,加入实例化的对象上没有某个属性,根据原型链就会去查找函数(类)身上的constructor
中存放的方法和属性
var btnBox = document.getElementById('btnBox');
inputs = btnBox.getElementsByTagName('input');
for(var i=0; i<inputs.length; i++){
inputs[i].onclick = function(){
alert(i);
}
}
事件绑定是异步变成,当触发点击行为,绑定的方法执行的时候,外层循环已经结束;方法执行产生私有作用域,用到变量i,不是私有的变量,按照作用域链查找的机制,查找的是全局下的
var btnBox = document.getElementById('btnBox');
inputs = btnBox.getElementsByTagName('input');
for(var i=0; i<inputs.length; i++){
inputs[i].onclick = (function(i){
return(function(){
alert(i);
})
})(i)
}
var fullName = 'language';
var obj = {
fullName: 'javascrtpt',
prop: {
getFullName: function(){
return this.fullName;
}
}
};
console.log(obj.prop.getFullName()); // this => obj.prop; obj.prop没有name属性; undefined
var test = obj.prop.getFullName;
console.log(test()); // this => window.fullName(严格模式); language
var name = 'window';
var Tom = {
name: 'Tom',
show: function(){
console.log(this.name); // 4. 'window'
},
wait: function(){
var fun = this.show; // 2. => this: Tom
fun(); // 3. => this: window
}
};
Tom.wait(); // 1. => this: Tom
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(); // 0
my_fun.c(); // this => my_fun.a = 30 ; 30
结果:0 30
my_fun.a
用来设置私有属性
my_fun.__proto__.a
用来设置公有属性
constructor
属性,导致类的原型构造函数缺失(解决:自己手动在堆内存中增加constructor
属性)function Fn(){
var n = 10;
this.m = 20;
this.aa = function(){
console.log(this.m);
}
}
Fn.prototype.bb = function(){
console.log(this.n);
}
var f1 = new Fn;
Fn.prototype = {
aa: function(){
console.log(this.m + 10);
}
};
var f2 = new Fn;
console.log(f1.constructor); // [Function: Fn]
console.log(f2.constructor); // f2.constructor => 找不到 => f2._prototype_.constructor => [Function: Object]
f1.bb(); // undefined
f1.aa(); // 20
f2.bb(); // TypeError: f2.bb is not a function
f2.aa(); // 20
f2.__proto__.aa(); // this.m + 10 => f2.__proto__.m + 10 => Fn.prototype.m + 10 => undefined + 10 => NaN
实现一个数组查重方法。
思路:
依次遍历数组中的每一项, 让每一项的值作2为对象的属性名和属性值(属性值存啥都可以),每一次存储之前验证当前对象中是2否已经存在这个属性了(in/hasOwnProperty/属性值不是undefined…),如果2有这个属性了,说明当前项在数组中已经存在J ,我们把当前项在原有数组中移除即可,如果不存在,存储到对象中即可
解答:
function unique(ary){
var obj = {};
for(var i=0; i<ary.length; i++){
var item = ary[i];
if(obj.hasOwnProperty(item)){
ary.splice(i, 1);
i--; // 处理数组塌陷
continue;
}
obj[item] = item;
}
return ary;
}
性能优化:把当前重复项赋值为数组最后一项,再删除数组最后一项
function unique(ary){
var obj = {};
for(var i=0; i<ary.length; i++){
var item = ary[i];
if(obj.hasOwnProperty(item)){
ary[i] = ary[ary.length. 1];
ary.pop();
i--; // 处理数组塌陷
continue;
}
obj[item] = item;
}
obj = null; // 手动释放内存
return ary;
}
这个例子unique
方法开辟的栈内存销毁,因为传入的ary
是一个对象的地址,返回的ary
也是一个同一个对象的地址,而这个对象是在外部定义的,所以外部并未占用unique
方法内部的任何变量,所以栈内存运行完成之后直接销毁。
基于内置类的原型扩展方法,供它的实例调取使用:
// 命名准则加一个前缀,区分原生Javascript提供的方法,`myUnique`可有可无
Array.prototype.myUnique = function myUnique() {
// 一般情况下(不使用__proto__和prototype调用方法),this指的就是调用该方法的数组本身
var obj = {};
for (var i = 0; i < this.length; i++) {
var item = this[i];
if (obj.hasOwnProperty(item)) {
ary[i] = ary[ary.length. 1];
ary.pop();
i--; // 处理数组塌陷
continue;
}
obj[item] = item;
}
obj = null; // 手动释放内存
// 不需要return,改变this就相当于操作原有数组
// 如果需要链式操作,可以返回
return this;
}
var ary = [11, 22, 22, 33, 1, 1, 111, 11, 2, 3, 4];
ary.myUnique()
console.log(ary);
怎么规避多人开发函数重名的问题?
基于单例实体模式来实现模块化开发(或闭包)。把实现当前模块的功能和属性放在同一个命名空间之下。
Javascript如何实现面向对象中的继承?
你理解的闭包作用是什么,优缺点?
闭包就是Javascript的一个机制,通过函数执行形成一个不销毁的私有作用域(栈内存),既保护了里面的私有变量,能让与外界隔离,又保存了变量的信息。总的来说闭包在项目中提供了保存和保护的两个功能。
对于保护功能,在封装插件时,为了不让插件中的方法与外界冲突,采用闭包可以保护这些方法命名。
对于保存功能,当使用循环事件绑定时,当调用循环变量i会成为全局作用域下的i,此时i指的是循环变量的最终状态,采用闭包机制可以在循环时形成一个不销毁的私有作用域,把后期需要用的索引提前保存起来。
惰性思想和柯里化模型都是基于闭包实现的。